在 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。