[元件] Provider
token
元件的實例化的過程會透過控制反轉容器 (IoC Container) 管理。
在單個 module 中實例化的順序是:
- provider
- controller
- module
註冊到容器之後, 可以透過 token 名稱取得 provider 實例。
下面是宣告 token 名稱的幾種方式:
useClass
在 module metadata 寫的 providers 預設是 useClass,例如 TodoService 這個類別定義會直接當成 token 名稱,因此 useClass 大多會直接縮寫:
@Module({ //... // 預設的 token 就是 useClass 格式,可以縮寫成這樣 providers: [TodoService],})export class TodoModule {}
@Module({ //... // 完整的格式 providers: [ { provide: TodoService, // token 名稱 useClass: TodoService, // token 回傳值 }, ],})export class TodoModule {}useValue
用字串設定 token 名稱並指定一個任意型別的回傳值:
{ provide: 'TEST_USE_VALUE', useValue: '這是用 useValue 註冊的 provider',}使用裝飾器 @Inject 並傳入 token 名稱,被 @Inject 修飾的參數 testUseValue 就是這個 provider 實例:
@Controller()export class AppController { constructor( // 在裝飾器傳入 token 名稱 @Inject('TEST_USE_VALUE') private readonly testUseValue: string, ) {}
@Get('test-use-value') getTestUseValue() { return this.testUseValue; }}useFactory
用 callback 的方式產生回傳值。
inject 可以直接存取到其他 provider,然後在 callback 中呼叫這個 provider 的方法:
{ provide: 'TEST_USE_FACTORY', inject: [AppService], useFactory: (appService: AppService) => { const result = appService.getHello();
return `這是用 useFactory 註冊的 provider,這是從 AppService 拿到的資料 ${result}`; },},同樣使用裝飾器 @Inject:
@Controller()export class AppController { constructor(@Inject('TEST_USE_FACTORY') private readonly testUseFactory: string) {}
@Get('test-use-factory') getTestUseFactory() { return this.testUseFactory; }}也支援非同步 callback:
{ provide: 'TEST_ASYNC_USE_FACTORY', inject: [AppService], useFactory: async (appService: AppService) => { const timeLimit = 1000; const result: string = await new Promise((resolve) => { setTimeout(() => { resolve(appService.getHello()); }, timeLimit); });
return `這是用 async useFactory 註冊的 provider,這是從 AppService 拿到的資料 ${result},耗時 ${timeLimit} 毫秒`; },},useExisting
自訂一個 token 名稱來存取已經存在的 provider 實例:
{ provide: 'TEST_USE_EXISTING', useExisting: AppService,},@Controller()export class AppController { constructor( // 實際上這個 provider 就是 AppService @Inject('TEST_USE_EXISTING') private readonly testUseExisting: AppService, ) {}
@Get('test-use-existing') getTestUseExisting() { return this.testUseExisting.getHello(); }}變數管理
provider 的格式由 Provider 這個型別規定,因此可以用來宣告出物件資料再填入 module 的 exports:
const testUseValueProvider: Provider = { provide: 'TEST_USE_VALUE', useValue: '這是用 useValue 註冊的 provider',};
@Module({ providers: [testUseValueProvider], exports: [testUseValueProvider],})export class CustomModule {}可選注入
依賴注入是在建構函式裡面以參數的方式傳入實例,使用 @Optional 表示該依賴是可選的,這樣注入時如果找不到相關的 provider 實例,也還是能順利啟動。
但是和標記 ? 的參數一樣,沒有傳入就會變 undefined:
@Controller()export class AppController { constructor( @Optional() @Inject('TEST_USE_EXISTING') private readonly testUseExisting: AppService, ) {}
@Get('test-use-existing') getTestUseExisting() { if (!testUseExisting) { return '實例不存在'; }
return this.testUseExisting.getHello(); }}小結
除了 controller 以外,大部分的邏輯元件都可以被歸類為 provider,只要留意匯入匯出。
到 provider 之後應該會對整個依賴注入和應用程式的基礎機制有點概念了!