Angular2 依赖注入

1. 使用DI

依赖注入是一个很重要的程序设计模式。 Angular 有自己的依赖注入框架,离开了它,我们几乎没法构建 Angular 应用。它使用得非常广泛,以至于几乎每个人都会把它简称为 DI。

我们来看一个简单的例子:


export class Animal {

dogs;

constructor() {

var dog = new Dog();

}

}

我们的Animal在构造函数中手工创建所需的每样东西。问题在于,我们这个 Animal类过于脆弱、缺乏弹性并且难以测试。

当我们的Animal 类需要一个 Dog,没有去请求一个现成的实例, 而是在构造函数中用具体的 Dog类新创建了一份只供自己用的副本。

如果 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 有一个多级依赖注入系统。实际上,应用程序中有一个与组件树平行的注入器树,我们可以在组件树中的任何级别上重新配置注入器。常见的注入器数的形式如下:

当一个底层的组件申请获得一个依赖时, 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, useClass: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 ) { }

时间: 2024-11-01 12:40:54

Angular2 依赖注入的相关文章

angular2 依赖注入新坑。

昨天经理说要改经纬度的格式,然后今天就着手搞. 搞了一上午,发现在原有的 input标签里做出来有瑕疵. 然后,下午就用angular2的 onchanges 监听来做. 做到后面,需要 传值的时候,自己写的测试应用就出毛病了. 在写依赖注入服务的时候,文件引用我是直接写在 index.ts里面的, 结果,就出毛病了.报错: Can't resolve all parameters for OnchanComponent: (?). 这是不认识注入的这个服务. 弄了许久,都没发现问题所在. 结果

angular2依赖注入——注入器的使用

一.显示注入器 injector = ReflectiveInjector.resolveAndCreate([Car, Engine, Tires]); let car = injector.get(Car); 二.单例服务 在一个注入器的范围内,依赖都是单例的,因为他们共享一个Service实例 三.多例服务 Angular DI是一个分层的依赖注入系统,这意味着嵌套的注入器可以创建他们自己的服务实例. 四.@injectabled的作用 标识一个类可以被注入器实例化. 通常,在试图实例化没

angular2的依赖注入

更好阅读体验,请看原文 在读这篇文章之前,你要先了解一下什么是依赖注入,网上关于这个的解释很多,大家可以自行Google. 我们这一篇文章还是以QuickStart项目为基础,从头开始讲解怎么在Angular2中使用依赖注入,如果你按照本篇文章中讲解的示例亲自走一遍的话,你一定能够掌握如何在Angular2中使用依赖注入.好,废话不多说,开始我们今天的旅行吧! 我们首先将项目中的内联模板替换为一个模板文件,使用templateUrl替换template: @Component({ selecto

Angular2基础03: 理解依赖注入

理解依赖注入injector 依赖注入是重要的程序设计模式. Angular 有自己的依赖注入框架,离开了它,几乎没法构建 Angular 应用.下面我分步来加强我对它的理解: 开门见山,什么是依赖注入:它是一种编程模式,可以让类从外部源中获得它的依赖,而不必亲自创建它们 第一步:为什么要解耦? 第二步:实现解耦

iOS控制反转(IoC)与依赖注入(DI)的实现

背景 最近接触了一段时间的SpringMVC,对其控制反转(IoC)和依赖注入(DI)印象深刻,此后便一直在思考如何使用OC语言较好的实现这两个功能.Java语言自带的注解特性为IoC和DI带来了极大的方便,要在OC上较好的实现这两个功能,需要一些小小的技巧. 控制反转和依赖注入 控制反转 简单来说,将一个类对象的创建由手动new方式改为从IOC容器内获取,就是一种控制反转,例如我们现在要创建一个ClassA类,则常规方法为 ClassA *a = [ClassA new]; 如果使用控制反转,

详解 Spring 3.0 基于 Annotation 的依赖注入实现(转)

使用 @Repository.@Service.@Controller 和 @Component 将类标识为 Bean Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发.@Repository 注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean.具体只需将该注解标注在 DAO 类上即可.同时,为了让 Spring 能够扫描类路径中的类并识别出 @Repository 注解,需要在 XML 配置文件中启用 Bean

工厂模式、控制反转及依赖注入

在介绍工厂模式与控制反转(Inversion of Control)及依赖注入(Dependency Injection)之前,先介绍下类的调用方法.目前调用方法总共有3种:1.自己创建:2.工厂模式:3.外部注入,其中外部注入即为控制反转/依赖注入模式(IoC/DI).我们可以用3个形象的东西来分别表示它们,就是new.get.set.顾名思义,new表示自己创建,get表示主动去取(即工厂),set表示是被别人送进来的(即注入),其中get和set分别表示了主动去取和等待送来两种截然相反的特

spring中依赖注入方式总结

Spring中依赖注入的四种方式 在Spring容器中为一个bean配置依赖注入有三种方式: · 使用属性的setter方法注入  这是最常用的方式: · 使用构造器注入: · 使用Filed注入(用于注解方式). 使用属性的setter方法注入 首先要配置被注入的bean,在该bean对应的类中,应该有要注入的对象属性或者基本数据类型的属性.例如:为UserBiz类注入UserDAO,同时为UserBiz注入基本数据类型String,那么这时,就要为UserDAO对象和String类型设置se

(七)理解angular中的module和injector,即依赖注入

依赖注入(DI)的好处不再赘言,使用过spring框架的都知道.angularjs作为前台js框架,也提供了对DI的支持,这是javascript/jquery不具备的特性.angularjs中与DI相关有angular.module().angular.injector(). $injector.$provide.对于一个DI容器来说,必须具备3个要素:服务的注册.依赖关系的声明.对象的获取.比如spring中,服务的注册是通过xml配置文件的<bean>标签或是注解@Repository.