Angular2文档学习的知识点摘要——依赖注入
目录
依赖注入(DI) Angular的依赖注入系统能够即时地创建和交付所依赖的服务。 constructor(private service: HeroService) { }
当 Angular 创建组件时,会首先为组件所需的服务请求一个注入器 (injector)。 为什么需要依赖注入?
Angular 依赖注入
import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
@Injectable()
export class HeroService {
getHeroes() { return HEROES; }
}
如果真的从远端服务器获取数据,这个 API 必须是异步的,可能得返回 ES2015 承诺 (promise)。 需要重新处理组件消费该服务的方式。 配置注入器不需要创建 Angular 注入器。 Angular 在启动过程中自动为我们创建一个应用级注入器。 platformBrowserDynamic().bootstrapModule(AppModule);
我们必须通过注册 在 NgModule 中注册提供商 通常会把提供商添加到根模块上,以便在任何地方使用服务的同一个实例。 @NgModule({
imports: [
BrowserModule
],declarations: [
AppComponent,/* . . . */
],providers: [
UserService,{ provide: APP_CONFIG,useValue: HERO_DI_CONFIG }
],bootstrap: [ AppComponent ]
})
export class AppModule { }
在组件中注册提供商 或者,也可以在@Component元数据中的providers属性中把它注册在组件层: 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:把它注册在组件级表示该组件的每一个新实例都会有一个服务的新实例。 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 标签还是通过路由导航到组件 —— 它都会自己管理好注入器的创建和调用。 单例服务 在一个注入器的范围内,依赖都是单例的。 在这个例子中, 当服务需要别的服务时 如果 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()?
注:注入器同时负责实例化像HerosComponent这样的组件。为什么不标记HerosComponent为@Injectable()呢?
注入器的提供商们
Provider类和provide对象常量像下面一样写 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具有相同接口。当旧组件想使用 [ NewLogger,// Not aliased! Creates two instances of `NewLogger` { provide: OldLogger,useClass: NewLogger}]
那么,应用中将会出现两个不同的 [ 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: () => {}
};
于是可以通过 [{ provide: Logger,useValue: silentLogger }]
工厂提供商 当服务的构造参数需要传入不能注入的类型的参数时,如果在providers处配置服务提供商的话,使用服务时将会出现错误,原因是创建该服务时缺少参数。如: 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);
}
我们可以注入 // 工厂提供商需要一个工厂方法:
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。 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 = this.injector.get(HeroService);
注:这里的令牌指的是Provider对象的provide属性的值,通过该值来查找到所需的依赖。 非类依赖 如果依赖值不是一个类呢?有时候想要注入的东西是一个字符串,函数或者对象。 export interface AppConfig {
apiEndpoint: string;
title: string;
}
export const HERO_DI_CONFIG: AppConfig = {
apiEndpoint: 'api.heroes.com',title: 'Dependency Injection'
};
我们想让这个配置对象在注入时可用,而且知道可以使用值提供商来注册一个对象。但是,这种情况下用什么作令牌呢? 注:可能有人会想到使用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){ }
对于习惯于在强类型的语言中使用依赖注入的开发人员,这会看起来很奇怪, 因为在强类型语言中,接口是首选的用于查找依赖的主键。 OpaqueToken解决方案是定义和使用 OpaqueToken(不透明的令牌)。定义方式类似于这样: import { OpaqueToken } from '@angular/core';
export let APP_CONFIG = new OpaqueToken('app.config');
使用这个 providers: [{ provide: APP_CONFIG,useValue: HERO_DI_CONFIG }]
现在,在 constructor(@Inject(APP_CONFIG) config: AppConfig) { this.title = config.title; }
注:虽然 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。 附录:为什么建议每个文件只放一个类在同一个文件中有多个类容易造成混淆,最好避免。 开发人员期望每个文件只放一个类。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |