Angular2 依赖注入

1. 使用DI

依赖注入是一个很重要的程序设计模式。 Angular 有本身的依赖注入框架,离开了它,咱们几乎无法构建 Angular 应用。它使用得很是普遍,以致于几乎每一个人都会把它简称为 DI。设计模式

咱们来看一个简单的例子:数组

export class Animal {网络

dogs;app

constructor() {框架

var dog = new Dog();ide

}函数

}测试

咱们的Animal在构造函数中手工建立所需的每样东西。问题在于,咱们这个 Animal类过于脆弱、缺少弹性而且难以测试。网站

当咱们的Animal 类须要一个 Dog,没有去请求一个现成的实例, 而是在构造函数中用具体的 Dog类新建立了一份只供本身用的副本。this

若是 Dog类升级了,而且它的构造函数要求传入一个参数了,该怎么办? 咱们这个Animal类就被破坏了,并且直到咱们把建立引擎的代码重写为 Dog= new Dog(theNewParameter) 以前,它都是坏的。可是当Dog类的定义发生变化时,咱们就不得不在意了,Animal类也不得不跟着改变。 这就会让Animal类过于脆弱。

如今,每一个Animal都有本身独特的Dog。他没法被其余Animal共享。咱们的Animal缺少必要的弹性,没法共享给其余的Animal类消费。

咱们该如何让 Animal更强壮、有弹性以及可测试?

答案超级简单。咱们把Animal的构造函数改形成使用 DI 的版本:

export class Animal {

dogs;

constructor(private dog:Dog) {

}

}

发生了什么?咱们把依赖的定义移到了构造函数中。 咱们的Animal类再也不建立Dog, 它仅仅“消费”它们。若是有人扩展了Dog类,那就再也不是Animal类的烦恼了。

2. Angular DI

Angular 自带了它本身的依赖注入框架。此框架也能被当作独立模块用于其它应用和框架中。

2.1 注入器树

Angular 有一个多级依赖注入系统。实际上,应用程序中有一个与组件树平行的注入器树,咱们能够在组件树中的任何级别上从新配置注入器。常见的注入器数的形式以下:

x

当一个底层的组件申请得到一个依赖时, Angular 先尝试用该组件本身的注入器来知足它。 若是该组件的注入器没有找到对应的提供商,它就把这个申请转给它父组件的注入器来处理。 若是那个注入器也没法知足这个申请,它就继续转给 它的父组件的注入器。 这个申请继续往上冒泡——直到咱们找到了一个能处理此申请的注入器或者超出了组件树中的祖先位置为止。 若是超出了组件树中的祖先还未找到, Angular 就会抛出一个错误。

2.2 实现原理

Angular给依赖注入器提供令牌来获取服务。一般在构造函数里面,为参数指定类型,让 Angular 来处理依赖注入。该参数类型就是依赖注入器所需的 令牌 。 Angular 把该令牌传给注入器,而后把获得的结果赋给参数。

注入器从哪儿获得的依赖? 它可能在本身内部容器里已经有该依赖了。 若是它没有,也能在 提供商 的帮助下新建一个。 提供商 就是一个用于交付服务的配方,它被关联到一个令牌。Angular会根据该令牌根据供应商建立一个服务结果返回,并将其保存在注入器内部供之后调用。

2.3 令牌

当咱们为注入器注册一个提供商时,其实是把这个提供商和一个 DI 令牌关联起来了。 注入器维护一个内部的 令牌 - 提供商 映射表,这个映射表会在请求一个依赖时被引用到。令牌就是这个映射表中的键值 key 。

2.3.1 类依赖

通常状况下,依赖值都是一个类 实例 ,而且类的类型是它本身的查找键值。 这种状况下,咱们其实是直接从注入器中以 类型做为令牌,来获取一个 实例。

写一个须要基于类的依赖注入的构造函数对咱们来讲是很幸运的。咱们只要以 类为类型,定义一个构造函数参数, Angular 就会知道把跟 类令牌关联的服务注入进来,大多数依赖值都是以类的形式提供的。

例如,其中Animal依赖Dog类,在构造函数中提供Dog类型,就能够依赖注入对应的Dog类实例。

export class Animal {

dogs;

constructor(private dog:Dog) {

}

}

请注意,TypeScript 接口不是一个有效的令牌。

2.3.2 非类依赖

若是依赖值不是一个类呢?有时候咱们想要注入的东西是一个字符串,函数或者对象。

应用程序常常为不少很小的因素 ( 好比应用程序的标题,或者一个网络 API 终点的地址 ) 定义配置对象,可是这些配置对象不老是类的实例。

可是这种状况下咱们要把什么用做令牌呢? 解决方案是定义和使用一个 OpaqueToken。定义方式相似于这样:

import { OpaqueToken } from '@angular/core';

export let APP_CONFIG = new OpaqueToken('app.config');

咱们使用这个 OpaqueToken 对象注册依赖的提供商:

providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]

如今,在 @Inject 的帮助下,咱们能够把这个配置对象注入到任何须要它的构造函数中:

constructor(@Inject(APP_CONFIG) config: AppConfig) {

this.title = config.title;

}

虽然 ConfigAppConfig 接口在依赖注入过程当中没有任何做用,但它为该类中的配置对象提供了强类型信息。

2.4 提供商

提供商 提供 依赖注入的一个运行时版本。 注入器依靠 提供商们 来建立服务的实例,它会被注入器注入到组件或其它服务中。

Angular中使用provide对象来做为提供商,该 provide 对象须要一个令牌 和一个定义对象,该令牌一般是一个类,但并不是必定是。

2.4.1 userValue-值提供商

该定义对象有一个主属性 ( 即userValue) ,用来标识该提供商会如何新建和返回依赖。

把一个固定的值 ,也就是该提供商能够将其做为依赖对象返回的值,赋给 userValue 属性。

一般使用该技巧来进行运行期常量设置 ,好比网站的基础地址和功能标志等。 咱们在OpaqueToken中已经见识了一个例子,咱们为APP_CONFIG提供了一个常量做为定义对象。

{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }

一个值提供商的值必需要当即定义。不能过后再定义它的值。很显然,标题字符串是马上可用的。

2.4.2 useClass -类提供商

userClass 提供商建立并返回一个指定类的新实例。使用该技术来为公共或默认类 提供备选实现。通常来讲,被新建的类同时也是该提供商的注入令牌,例如一个提供日志服务的提供商

{ provide: LoggerService, useClass:LoggerService }

咱们在依赖注入LoggerService时,会根据类LoggerService来建立一个默认的示例做为结果返回。

当依赖注入的类有其余类型依赖的状况下,例如LoggerService依赖于用户信息,咱们一样用构造函数注入模式,来添加一个带有 LoggerService参数的构造函数。这种状况下就须要用到Injectable注解。

@Injectable() 标志着一个类能够被一个注入器实例化。当咱们的LoggerService服务有了一个注入的依赖,咱们须要使用@Injectable()来标识LoggerService,这样Angular 就可使用构造函数参数的元数据来注入一个 用户服务

2.4.3 useExisting - 别名提供商

useExisting ,提供商能够把一个令牌映射到另外一个令牌上。实际上,第一个令牌是第二个令牌所对应的服务的一个别名,创造了访问同一个服务对象的两种方法 。

{ provide: MinimalLogger, useExisting:LoggerService }

经过使用别名接口来把一个 API 变窄,是一个很重要的该技巧的使用例子。咱们在这里就是为了这个目的使用的别名。 想象一下若是 LoggerService 有个很大的 API 接口 ( 虽然它其实只有三个方法,一个属性 ) ,经过使用 MinimalLogger 类 - 接口别名,就能成功的把这个 API 接口缩小到只暴露两个成员:

export abstract class MinimalLogger {

logInfo: (msg: string) => void;

logs: string[];

}

2.4.4 useFactory工厂提供商

useFactory 提供商经过调用工厂函数来新建一个依赖对象,主要用来建立一个拥有参数的对象来做为提供者。

使用这项技术,能够用包含了一些 依赖服务和本地状态 输入的工厂函数来 创建一个依赖对象,该依赖对象不必定是一个类实例,它能够是任何东西。例如

export function factory(level){

return new Logger(level)

}

{ provide: RUNNERS_UP, useFactory: factory, deps: [2] }

2.5 配置注入器。

通常来讲输入器的位置有两种,一种是在NgModule中注入,一种是在Component中注入。两种类型都是在元数据中的providers数组中注入,区别在在生效的范围不一样,Component中注入只在当前组件以及子组件中生效。例如

Providers:[

UserService,

{ provide: Logger, useClass: EvenBetterLogger }

]

其中UserService便是类提供商的简写

{ provide: UserService, useClass: UserService}

2.6 使用DI

咱们知道了如何配置服务提供商,如今咱们来了解一下如何使用。

2.6.1 构造函数

一般状况下咱们使用构造函数参数来注入对应的服务。通常来说主要存在两种状况。

首先,是类型做为令牌的依赖注入,这种状况下,能够直接使用构造函数中的参数类型进行依赖注入,例如在Animal中使用Dog类型

export class Animal {

dogs;

constructor(private dog:Dog) {

}

}

其次,可使用@Inject(‘token’)的形式注入令牌的类型的对象或者服务,例如咱们注入值类型的配置对象

constructor(@Inject(APP_CONFIG) config: AppConfig) {

this.title = config.title;

}

2.6.2 获取父组件

一般来讲获取父组件就是获取一个已经存在的组件类型,父组件必须经过提供一个与别名提供者来实现,例如

providers: [{provide: Parent, useExisting: forwardRef(() => ParentComponent) }]

Parent 是该提供商的令牌, ParentComponent就是该别名提供商的类型,将该类型提供商注入到父组件的注入器中,则子组件可使用Parent令牌做为构造函数参数类型来注入该服务,获取ParentComponent。 ParentComponent引用了自身,形成循环引用,必须使用 前向引用forwardRef 打破了该循环,查找当前或者父级的提供商。

2.6.3 跳过自身与可选

当咱们不想从当前元素获取依赖的时候,可使用@SkipSelf(),这样注入器从一个在本身 上一级 的组件开始搜索一个 Parent 依赖。同时,当没法确保依赖是否存在的状况下,而又为了不抛出找不到依赖的错误状况,可使用@Optional()注解,这样该依赖是可选的,例如引入父组件的构造函数能够写成以下的格式:

constructor( @SkipSelf() @Optional() public parent: Parent ) { }

相关文章
相关标签/搜索