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 ) { }

Angular2 依赖注入的更多相关文章

  1. angular2系列教程(八)In-memory web api、HTTP服务、依赖注入、Observable

    大家好,今天我们要讲是angular2的http功能模块,这个功能模块的代码不在angular2里面,需要我们另外引入: index.html <script src="lib/htt ...

  2. angular2 的依赖注入

    angular2 的依赖注入包含了太多的内容,其中的一个重点就是注入器,而注入器又非常难理解,今天我们不深入介绍注入器的内容,可以参考官方文档,我们今天来说注入器的层级. 也就是组件获取服务的容器会选 ...

  3. angular2的依赖注入

    更好阅读体验,请看原文 在读这篇文章之前,你要先了解一下什么是依赖注入,网上关于这个的解释很多,大家可以自行Google. 我们这一篇文章还是以QuickStart项目为基础,从头开始讲解怎么在Ang ...

  4. angular2 学习笔记 ( DI 依赖注入 )

    refer : http://blog.thoughtram.io/angular/2016/09/15/angular-2-final-is-out.html ( search Dependency ...

  5. 迈向angularjs2系列(5):依赖注入

    一: 为什么要依赖注入 1.构造器引入依赖 假设一个类Car类依赖于Engine(引擎)类.Transition(变速箱)类,可使用构造器来完成. //类似如下代码 class Engine{} cl ...

  6. webapi - 使用依赖注入

    本篇将要和大家分享的是webapi中如何使用依赖注入,依赖注入这个东西在接口中常用,实际工作中也用的比较频繁,因此这里分享两种在api中依赖注入的方式Ninject和Unity:由于快过年这段时间打算 ...

  7. ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入

    原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...

  8. 在WPF中使用依赖注入的方式创建视图

    在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...

  9. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

随机推荐

  1. ASP.NET WEB API 帮助文档引用单独项目中的DTO,见面上不显示字段注释问题解决办法

    StackOverFlow上的解决办法: 问题地址

  2. 使用PrintDocument进行打印

    背景: 1.在winform中,需要直接调用打印机,进行打印处理 2.找了很多实现方法是web的处理,然后查了下度娘,发现可以使用自带类PrintDocument进行处理,所以就有了这篇文章 说明: ...

  3. py-faster-rcnn几个辅助脚本

    py-faster-rcnn本身代码很棒. 不过使用它的时候,还是需要自己写一些脚本,作为辅助. 1 所有.py文件顶部添加utf8编码声明.因为有时候需要添加中文注释,不声明编码会报错 #inser ...

  4. FlowLayoutPanel

    动态生成控件  按顺序规律排列时 用panel的话 要指定特定的位置 .麻烦. 可以通过用flowLayoutPanel来解决. FlowLayoutPanel:表格布局面板,适合以表格形式规则的动态 ...

  5. POJ 3278 Catch That Cow(bfs)

    传送门 Catch That Cow Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 80273   Accepted: 25 ...

  6. Web系统大规模并发——电商秒杀与抢购

    电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要.这次我们 ...

  7. 获取URL中的参数

    function GetQueryString(name){ var reg = new RegExp("(^|&)"+ name +"=([^&]*)( ...

  8. Tengine 安装配置全过程

    Tengine官网上有个非常简单的教程,中间并未涉及到一些常用的设置,所以仅供参考.一下午为本人的安装步骤及过程. 1.安装必要的编译环境好 由于Tengine安装需要使用源代码自行编译,所以在安装前 ...

  9. React的井字过三关(2)

    这篇笔记是官方教程的延续笔记,所有代码基于第一篇笔记的结尾代码.旨在解决教程后面提出的五个问题. 一 . 用(X,Y)来取代原有的数字坐标 原来的数字坐标是这样的: 现在的要求是把原来的代码坐标改为二 ...

  10. JDK Collection 源码分析(3)—— Queue

    @(JDK)[Queue] JDK Queue Queue:队列接口,对于数据的存取,提供了两种方式,一种失败会抛出异常,另一种则返回null或者false.   抛出异常的接口:add,remove ...