在 Angular 2 中,provider 的 token 的类型能够是字符串或 Type 类型。咱们能够根据实际应用场景,选用不一样的类型。假设咱们有一个服务类 DataService,而且咱们想要在组件中注入该类的实例,咱们能够这样使用:html
@Component({ selector: 'my-component', providers: [ { provide: DataService, useClass: DataService } ] }) class MyComponent { constructor(private dataService: DataService) { } }
// Type类型 - @angular/core/src/type.ts export const Type = Function; export function isType(v: any): v is Type<any> { return typeof v === 'function'; } export interface Type<T> extends Function { new (...args: any[]): T; }
这是很是酷炫的事情,只要咱们知道依赖对象的类型,咱们就能够方便地注入对应类型的实例对象。可是有时候,咱们须要注入的是普通的JavaScript对象,而不是Type 类型的对象。好比,咱们须要注入一个config对象:api
const CONFIG = { apiUrl: 'http://my.api.com', theme: 'suicid-squad', title: 'My awesome app' };
有时候咱们须要注入一个原始数据类型的数值,如字符串或布尔值:angular2
const FEATURE_ENABLED = true;
在这种状况下,咱们是不能使用 String 或 Boolean 类型,由于若是使用这些类型,咱们只能得到类型对应的默认值。想解决这个问题,但咱们又不想引入一种新的类型来表示原始数据类型的值。这时咱们能够考虑使用字符串做为 token,而不用引入新的类型:app
let featureEnabledToken = 'featureEnable'; let configToken = 'config'; providers: [ { provide: featureEnabledToken, useValue: FEATURE_ENABLED }, { provide: configToken, useValue: CONFIG } ]
使用字符串做为 token 设置完 providers 后,咱们就可使用 @Inject 装饰器注入相应依赖:ide
import { Inject } from '@angular/core'; class MyComponent { constructor( @Inject(featureEnabledToken) private featureEnabled, @Inject(configToken) private config ) }
让咱们回顾一下以前的例子,config 是一个很通用的名字,这样的话就可能在项目中留下隐患。由于若在项目中也存在一样名称的 provider,那么后面声明的 provider 将会覆盖以前声明的 provider。 函数
假设在项目中,咱们引入了第三方脚本库。该库的 provides 的配置信息以下:ui
export const THIRDPARTYLIBPROVIDERS = [ { provide: 'config', useClass: ThirdParyConfig } ];
实际使用时,咱们可能这样作:this
import THIRDPARTYLIBPROVIDERS from './third-party-lib'; providers = [ DataService, THIRDPARTYLIBPROVIDERS ];
到目前为止,一切都能正常工做。但咱们是不知道 THIRDPARTYLIBPROVIDERS 内部的具体状况,除非咱们已经阅读了第三方库的官方文档或源码。在未知的状况下,咱们可能这样使用:code
providers = [ DataService, THIRDPARTYLIBPROVIDERS, { provide: configToken, useValue: CONFIG } ];
此时第三方库就不能正常工做了。由于它获取不到它所依赖的配置对象,由于它被咱们自定义的 provider 替换了。component
为了解决上述问题,Angular 2 引入了 OpaqueToken,它容许咱们建立基于字符串的 Token 类。建立 OpaqueToken 对象很简单,只需导入 Opaque 类。这样的话,上面提到的第三方类库,能够调整为:
import { OpaqueToken } from '@angular/core'; const CONFIG_TOKEN = new OpaqueToken('config'); export const THIRDPARTYLIBPROVIDERS = [ { provide: CONFIG_TOKEN, useClass: ThirdPartyConfig } ];
而以前提到的冲突问题,也能够按照下面的方式解决。
import { OpaqueToken } from '@angular/core'; import THIRDPARTYLIBPROVIDERS from './third-party-lib'; const MY_CONFIG_TOKEN = new OpaqueToken('config'); providers = [ DataService, THIRDPARTYLIBPROVIDERS, { provide: MY_CONFIG_TOKEN, useValue: CONFIG } ]
// OpaqueToken - @angular/core/src/di/injection_token.ts export class OpaqueToken { constructor(protected _desc: string) {} toString(): string { return `Token ${this._desc};` } }
经过查看 OpaqueToken 类,咱们能够发现,尽管是使用相同的字符串去建立 OpaqueToken 实例对象,但每次都是返回一个新的实例,从而保证了全局的惟一性。
const TOKEN_A = new OpaqueToken('token'); const TOKEN_B = new OpaqueToken('token'); TOKEN_A === TOKEN_B // false
救世主 - OpaqueToken 不给力了
让咱们看一下示例中 DataService Provider 配置信息:
const API_URL = new OpaqueToken('apiUrl'); providers: [ { provide: DataService, useFactory: (http, apiUrl) => { // create data service }, deps: [ Http, new Inject(API_URL) ] } ]
咱们使用工厂函数建立 DataService 实例,DataService 依赖 http 和 apiUrl 对象,为了让 Angular 可以准确地注入依赖对象,咱们使用 deps 属性声明依赖对象的类型。由于 Http 是 Type 类型的 Token,咱们只需直接声明。但 API_URL 是 OpaqueToken 类的实例,不属于 Type 类型。所以咱们须要使用 new Inject(API_URL) 方式声明依赖对象。(备注:new Inject()与在构造函数中使用 @Inject() 的方式声明依赖对象是等价的)。
上面的代码可以正常运行,但在实际开发过程当中,开发者很容易忘记调用 new Inject()。为了解决这个问题,Angular 团队引入了 InjectionToken。
// InjectionToken - @angular/core/src/di/injection_token.ts /** * InjectionToken 继承于 OpaqueToken,同时支持泛型,用于描述依赖对象的类型 * */ export class InjectionToken<T> extends OpaqueToken { private _differentiate_from_OpaqueToken_structurally: any; constructor(desc: string) { super(desc); } toString(): string { return `InjectionToken ${this._desc};` } }
使用 InjectionToken 重写上面的示例:
// InjectionToken<T> - 使用泛型描述该Token所关联的依赖对象的类型 const API_URL = new InjectionToken<string>('apiUrl'); providers: [ { provide: DataService, useFactory: (http, apiUrl) => { // create data service }, deps: [ Http, API_URL // no `new Inject()` needed! ] } ]
咱们能够经过 OpaqueToken 避免定义 Provider 时,出现 Token 命名冲突的问题。除此以外,使用 OpaqueToken 也为咱们提供更好的异常信息。但若是咱们使用的 Angular 4.x 以上的版本,咱们最好使用 InjectionToken 替换原有的 OpaqueToken。