Angular 2 OpaqueToken & InjectionToken

在 Angular 2 中,provider 的 token 的類型能夠是字符串或 Type 類型。咱們能夠根據實際應用場景,選用不一樣的類型。假設咱們有一個服務類 DataService,而且咱們想要在組件中注入該類的實例,咱們能夠這樣使用:html

@Component({
  selector: 'my-component',
  providers: [
    { provide: DataService, useClass: DataService }
  ]
})
class MyComponent {
  constructor(private dataService: DataService) { }
}

Type 類型

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

使用字符串做爲 Token 存在的問題

讓咱們回顧一下以前的例子,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

救世主 - OpaqueToken

爲了解決上述問題,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 的工做原理

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

新救世主 - Angular 4.x 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。

參考資源

相關文章
相關標籤/搜索