目录

  • 目录
  • 依赖注入DI
    • 为什么需要依赖注入
    • Angular 依赖注入
      • 配置注入器
      • 在 NgModule 中注册提供商
      • 在组件中注册提供商
      • 该用NgModule还是应用组件
      • 注入服务
      • 显性注入器的创建
      • 单例服务
      • 当服务需要别的服务时
      • 为什么要用 Injectable
    • 注入器的提供商们
      • Provider类和provide对象常量
      • 备选的类提供商
      • 带依赖的类提供商
      • 别名类提供商
      • 值提供商
      • 工厂提供商
    • 依赖注入令牌
      • 非类依赖
        • OpaqueToken
    • 可选依赖
    • 附录为什么建议每个文件只放一个类

依赖注入(DI)

  Angular的依赖注入系统能够即时地创建和交付所依赖的服务。
  “依赖注入”是提供类的新实例的一种方式,还负责处理好类所需的全部依赖。大多数依赖都是服务。 Angular 使用依赖注入来提供新组件以及组件所需的服务。
  Angular 通过查看构造函数的参数类型得知组件需要哪些服务。 例如,HeroListComponent组件的构造函数需要一个HeroService服务:

constructor(private service: HeroService) { }

  当 Angular 创建组件时,会首先为组件所需的服务请求一个注入器 (injector)
  注入器维护了一个服务实例的容器,存放着以前创建的实例。 如果所请求的服务实例不在容器中,注入器就会创建一个服务实例,并且添加到容器中,然后把这个服务返回给 Angular。 当所有请求的服务都被解析完并返回时,Angular 会以这些服务为参数去调用组件的构造函数。 这就是依赖注入 。
  必须在要求注入HeroService之前,在注入器中注册HeroService提供商 Provider。 提供商用于创建并返回一个服务,通常是服务类本身。通过注册提供商,注入器才可以在没有服务的情况下,根据提供商创建对应的服务。

为什么需要依赖注入?

  1. 依赖注入是一种编程模式,可以让类从外部源中获得它的依赖,而不必亲自创建它们。

Angular 依赖注入

  1. Angular 附带了自己的依赖注入框架。此框架也能被当做独立模块用于其它应用和框架中。
  2. 通过像如下的代码一样创建服务:
import { Injectable } from '@angular/core';
import { HEROES }     from './mock-heroes';
@Injectable()
export class HeroService {
  getHeroes() { return HEROES;  }
}

  如果真的从远端服务器获取数据,这个 API 必须是异步的,可能得返回 ES2015 承诺 (promise)。 需要重新处理组件消费该服务的方式。
3. 服务只是 Angular 中的一个类。 有 Angular 注入器注册它之前,没有任何特别之处。

配置注入器

  不需要创建 Angular 注入器。 Angular 在启动过程中自动为我们创建一个应用级注入器。

platformbrowserDynamic().bootstrapModule(AppModule);

  我们必须通过注册提供商 (provider) 来配置注入器,这些提供商为应用创建所需服务。
  或者在 NgModule 中注册提供商,或者在应用组件中。

在 NgModule 中注册提供商

  通常会把提供商添加到根模块上,以便在任何地方使用服务的同一个实例。
  如在AppModule中注册LoggerUserServiceAPP_CONfig提供商。
app/app.module.ts

@NgModule({
  imports: [
    browserModule
  ],declarations: [
    AppComponent,/* . . . */
  ],providers: [
    UserService,{ provide: APP_CONfig,useValue: HERO_DI_CONfig }
  ],bootstrap: [ AppComponent ]
})
export class AppModule { }

在组件中注册提供商

  或者,也可以在@Component元数据中的providers属性中把它注册在组件层:
下面是更新的HerosComponent,它注册了HeroService。

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

import { HeroService }        from './hero.service';

@Component({
  selector: 'my-heroes',providers: [HeroService],template: ` <h2>Heroes</h2> <hero-list></hero-list> `
})
export class HeroesComponent { }

  注-1:把它注册在组件级表示该组件的每一个新实例都会有一个服务的新实例。
  注-2:如果忘了注册提供商时,Angular回在首次查找该服务时,抛出一个异常,如:

EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)
(异常:Logger类没有提供商!(HeroListComponent -> HeroService -> Logger))

该用NgModule还是应用组件?

  一方面,NgModule 中的提供商是被注册到根注入器。这意味着在 NgModule 中注册的提供商可以被整个应用访问。
  另一方面,在应用组件中注册的提供商只在该组件及其子组件中可用。

注入服务

  遵照依赖注入模式的要求,组件必须在它的构造函数中请求这些服务。

export class HeroListComponent {
  heroes: Hero[];
  constructor(heroService: HeroService) {
    this.heroes = heroService.getHeroes();
  }
}

显性注入器的创建

injector = ReflectiveInjector.resolveAndCreate([Car,Engine,Tires]);
let car = injector.get(Car);

  在必要时,可以写使用显式注入器的代码,但却很少这样做。 当 Angular 创建组件时 —— 无论通过像这样的 HTML 标签还是通过路由导航到组件 —— 它都会自己管理好注入器的创建和调用。

单例服务

  在一个注入器的范围内,依赖都是单例的。 在这个例子中,HeroesComponent和它的子组件HeroListComponent共享同一个HeroService实例。
  然而,Angular DI 是一个分层的依赖注入系统,这意味着嵌套的注入器可以创建它们自己的服务实例。

当服务需要别的服务时

  如果HeroService也有依赖,需要通过日志服务来汇报自己的活动呢?我们同样用构造函数注入模式,来添加一个带有Logger参数的构造函数。
未注入Logger服务:

import { Injectable } from '@angular/core';
import { HEROES }     from './mock-heroes';
@Injectable()
export class HeroService {
  getHeroes() { return HEROES;  }
}

注入Logger服务:

import { Injectable } from '@angular/core';
import { HEROES }     from './mock-heroes';
import { Logger }     from '../logger.service';
@Injectable()
export class HeroService {
  constructor(private logger: Logger) {  }
  getHeroes() {
    this.logger.log('Getting heroes ...');
    return HEROES;
  }
}

为什么要用 @Injectable()?

  1. @Injectable() 标识一个类可以被注入器实例化。 通常,在试图实例化没有被标识为@Injectable()的类时,注入器会报错。(我们可以理解为用@Injectable标识的类可以被注入服务)
  2. 建议为每个服务类都添加@Injectable(),包括那些没有依赖严格来说并不需要它的。因为:
    • 面向未来:没有必要记得在后来添加依赖的时候添加 @Injectable()
    • 一致性:所有的服务都遵循同样的规则,不需要考虑为什么某个地方少了一个。

注:注入器同时负责实例化像HerosComponent这样的组件。为什么不标记HerosComponent为@Injectable()呢?
  我们可以添加它。但是没有必要,因为HerosComponent已经有@Component装饰器了, @Component(和随后将会学到的@Directive和@Pipe一样)是 Injectable 的子类型。 实际上,正是这些Injectable装饰器是把一个类标识为注入器实例化的目标。

  1. 在运行时,注入器可以从编译后的 JavaScript 代码中读取类的元数据,并使用构造函数的参数类型信息来决定注入什么。
    注:总是使用@Injectable()的形式,不能只用@Injectable。 如果忘了括号,应用就会神不知鬼不觉的失败!

注入器的提供商们

  1. 提供商提供依赖值的一个具体的、运行时的版本。
  2. 注入器依靠提供商创建服务的实例,注入器再将服务的实例注入组件或其它服务。
  3. 必须为注入器注册一个服务的提供商,否则它不知道该如何创建该服务。

Provider类和provide对象常量

像下面一样写providers数组:

providers: [Logger]

这其实是用于注册提供商的简写表达式。 使用的是一个带有两个属性的提供商对象字面量:

[{ provide: Logger,useClass: Logger }]

第一个是令牌 (token),它作为键值 (key) 使用,用于定位依赖值和注册提供商。
第二个是供应商定义对象。 可以把它看做是指导如何创建依赖值的配方。 有很多方式创建依赖值…… 也有很多方式可以写配方。

备选的类提供商

  某些时候,我们会请求一个不同的类来提供服务。 下列代码告诉注入器,当有人请求Logger时,返回BetterLogger。

[{ provide: Logger,useClass: BetterLogger }]

带依赖的类提供商

假设EvenBetterLogger可以在日志消息中显示用户名。 这个日志服务从注入的UserService中取得用户, UserService通常也会在应用级注入。

@Injectable()
class EvenBetterLogger extends Logger {
  constructor(private userService: UserService) { super(); }

  log(message: string) {
    let name = this.userService.user.name;
    super.log(`Message to ${name}: ${message}`);
  }
}

就像之前在BetterLogger中那样配置它。

[ UserService,{ provide: Logger,useClass: EvenBetterLogger }]

别名类提供商

  服务OldLogger和NewLogger具有相同接口。当旧组件想使用OldLogger记录消息时,我们希望改用NewLogger的单例对象来记录。
  如果采用如下方式(备选的类提供商):

[ NewLogger,// Not aliased! Creates two instances of `NewLogger`  { provide: OldLogger,useClass: NewLogger}]

  那么,应用中将会出现两个不同的NewLogger实例。但是我们要求:不管组件请求的是新的还是旧的日志服务,依赖注入器注入的都应该是同一个单例对象。这时,我们应该使用useExisting选项指定别名。

[ NewLogger,// Alias OldLogger w/ reference to NewLogger
  { provide: OldLogger,useExisting: NewLogger}]

值提供商

有时,提供一个预先做好的对象会比请求注入器从类中创建它更容易。

// An object in the shape of the logger service
let silentLogger = {
  logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],log: () => {}
};

于是可以通过useValue选项来注册提供商,它会让这个对象直接扮演 logger 的角色。

[{ provide: Logger,useValue: silentLogger }]

工厂提供商

  当服务的构造参数需要传入不能注入的类型的参数时,如果在providers处配置服务提供商的话,使用服务时将会出现错误,原因是创建该服务时缺少参数。如:
hero.service.ts

constructor( private logger: Logger,private isAuthorized: boolean) { } getHeroes() { let auth = this.isAuthorized ? 'authorized ' : 'unauthorized'; this.logger.log(`Getting heroes for ${auth} user.`);
  return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
}

  我们可以注入Logger,但是不能注入逻辑型的isAuthorized。 我们不得不通过通过工厂提供商创建这个HeroService的新实例。
hero.service.provider.ts

// 工厂提供商需要一个工厂方法:
let heroServiceFactory = (logger: Logger,userService: UserService) => {
  return new HeroService(logger,userService.user.isAuthorized);
};

// 同时把Logger和UserService注入到工厂提供商中,并且让注入器把它们传给工厂方法
export let heroServiceProvider =
  { provide: HeroService,useFactory: heroServiceFactory,deps: [Logger,UserService]
  };

注:useFactory字段告诉 Angular:这个提供商是一个工厂方法,它的实现是heroServiceFactory。
  deps属性是提供商令牌数组。 Logger和UserService类作为它们自身类提供商的令牌。 注入器解析这些令牌,把相应的服务注入到工厂函数中相应的参数中去。

HeroService提供商

import { Component }          from '@angular/core';
import { HeroService }        from './hero.service';
@Component({
  selector: 'my-heroes',template: ` <h2>Heroes</h2> <hero-list></hero-list> `
})
export class HeroesComponent { }

heroServiceFactory工厂提供商

import { Component }          from '@angular/core';
import { heroServiceProvider } from './hero.service.provider';
@Component({
  selector: 'my-heroes',template: ` <h2>Heroes</h2> <hero-list></hero-list> `,providers: [heroServiceProvider]
})
export class HeroesComponent { }

依赖注入令牌

  当向注入器注册提供商时,实际上是把这个提供商和一个 DI 令牌关联起来了。 注入器维护一个内部的令牌-提供商映射表,这个映射表会在请求依赖时被引用到。 令牌就是这个映射表中的键值。
  在前面的所有例子中,依赖值都是一个类实例,并且类的类型作为它自己的查找键值。 在下面的代码中,HeroService类型作为令牌,直接从注入器中获取HeroService 实例:

heroService: HeroService = this.injector.get(HeroService);

注:这里的令牌指的是Provider对象的provide属性的值,通过该值来查找到所需的依赖。

非类依赖

  如果依赖值不是一个类呢?有时候想要注入的东西是一个字符串,函数或者对象。
  应用程序经常为很多很小的因素定义配置对象(例如应用程序的标题或网络API终点的地址)。 但是这些配置对象不总是类的实例,它们可能是对象,如下面这个:
app-config.ts

export interface AppConfig {
  apiEndpoint: string;
  title: string;
}

export const HERO_DI_CONfig: AppConfig = {
  apiEndpoint: 'api.heroes.com',title: 'Dependency Injection'
};

  我们想让这个配置对象在注入时可用,而且知道可以使用值提供商来注册一个对象。但是,这种情况下用什么作令牌呢?

注:可能有人会想到使用AppConfig作为令牌。但是TypeScript 接口不是一个有效的令牌。
CONfig常量有一个接口:AppConfig。不幸的是,不能把 TypeScript 接口用作令牌:

// FAIL! Can't use interface as provider token
[{ provide: AppConfig,useValue: HERO_DI_CONfig })]
// FAIL! Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ }

对于习惯于在强类型的语言中使用依赖注入的开发人员,这会看起来很奇怪, 因为在强类型语言中,接口是首选的用于查找依赖的主键。
这不是 Angular 的错。接口只是 TypeScript 设计时 (design-time) 的概念。JavaScript 没有接口。 TypeScript 接口不会出现在生成的 JavaScript 代码中。 在运行期,没有接口类型信息可供 Angular 查找。

Opaquetoken

  解决方案是定义和使用 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接口在依赖注入过程中没有任何作用,但它为该类中的配置对象提供了强类型信息。
或者在 ngModule 中提供并注入这个配置对象,如AppModule。
app/app.module.ts

providers: [
  UserService,{ provide: APP_CONfig,useValue: HERO_DI_CONfig }
],

可选依赖

  HeroService需要一个Logger,但是如果想不提供 Logger 也能得到它,该怎么办呢? 可以把构造函数的参数标记为@Optional(),告诉 Angular 该依赖是可选的:

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

HeroService中

constructor(@Optional() private logger: Logger) { if (this.logger) { this.logger.log(some_message); }
}

当使用@Optional()时,代码必须准备好如何处理空值。 如果其它的代码没有注册一个 logger,注入器会设置该logger的值为空 null。

附录:为什么建议每个文件只放一个类

在同一个文件中有多个类容易造成混淆,最好避免。 开发人员期望每个文件只放一个类。
如果我们蔑视这个建议,并且 —— 比如说 —— 把HeroService和HeroesComponent组合在同一个文件里,就得把组件定义放在最后面! 如果把组件定义在了服务的前面, 在运行时抛出空指针错误。
注:在forwardRef()方法的帮助下,实际上也可以先定义组件。 但是为什么要先给自己找麻烦呢? 还是通过在独立的文件中定义组件和服务,完全避免此问题吧。

Angular2文档学习的知识点摘要——依赖注入的更多相关文章

  1. Xcode C开发,需要澄清

    我非常喜欢Xcode提供对该语言可能的成员函数的深入了解的方式,并且更喜欢相对于文本伙伴使用它,如果不是因为我今天注意到的奇怪.当strings=“Teststring”时;唯一可用的substr签名如图所示但据我所知,签名应该是什么iseeonline确实s.substr(1,2);既被理解也适用于Xcode.当我尝试方法完成时为什么不显示?

  2. xamarin.ios – 没有找到ViewController ::.ctor(System.IntPtr)的构造函数

    我有一个问题,我的Monotouch应用程序有时在收到内存警告后才会崩溃.请参见下面的堆栈跟踪.堆栈跟踪是正确的,因为指定的类缺少构造函数获取IntPtr参数.但是这是有意的,因为我在应用程序中根本不使用InterfaceBuilder.那为什么会这样呢?

  3. ios – Swift – NSURL错误

    尝试使用下面的NSURL类时出错,下面的代码实际上是试图将我从Facebook拉入的图像存储到imageView中.错误如下:不知道为什么会这样,帮忙!解决方法你正在调用的NSURL构造函数有这个签名:?表示构造函数可能不返回值,因此它被视为可选.NSData构造函数也是如此:快速解决方法是:最好的解决方案是检查(解包)这些选项,即使您确定它们包含值!

  4. 如何在Xcode中追踪“libc abi.dylib:纯虚函数!”

    我有一个多线程OSX应用程序,它使用C,Objective-C和Swift的混合.当我的应用程序关闭时,我在Xcode调试器窗口中看到了这一点:我知道这个错误通常是由对C类构造函数或析构函数中的虚函数的调用引起的.有没有一种简单的方法可以找到它的位置?

  5. Swift实现对象归档

    Swift实现对象归档时有几个注意点要继承NSCoding,实现两个方法extension是一个分类,分类不允许有存储能力,所以协议方法不能写在分类中协议中的init(coderdecoder:NSCoder)函数会覆盖原始的构造函数,所以类中至少还要有另一个init方法如果不指定键名,会使用属性名称作为key,基本数据类型,需要指定key

  6. 【Swift初见】Swift构造过程

    构造过程是通过构造器来实现的,其实每个构造器就可以看作是一个函数,只是这个函数是为了执行初始化的。每个类都必须拥有一个指定构造器。

  7. swift的struct结构体类型介绍使用

  8. swift struct

    //:Playground-noun:aplacewherepeoplecanplayimportCocoavarstr="Hello,playground"structpoint{varx=0;vary=init(x:Int,y:Int){self.x=x;y=y;println("init");}funcgetCenter()->Int{return(x+y)/2;}mutatingfunca

  9. 《The Swift Programming Language》2.0版之自动引用计数

    Swift1.0文档翻译:TimothyYeSwift1.0文档校对:HawsteinSwift2.0文档校对及翻译润色:ChannePS:之前1.0版中文版看不懂地方在对比英文版后就懂了,还是之前翻译的不够准确啊。,而不是Person),它们的值会被自动初始化为nil,目前还不会引用到Person类的实例。由于Person类的新实例被赋值给了reference1变量,所以reference1到Person类的新实例之间建立了一个强引用。在你将john和number73赋值为nil后,强引用关系如下图:P

  10. swift #6 类

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部