前幾天都在分享 Angular Dependency Injection 的相關內容,今天還是要分享 DI。
今天這個 Tip 算是入門級:
export class AppComponent implements OnInit {
title = 'day4';
constructor(@Inject(MY_SERVICE_TOKEN) private mySrv: IMyService) {}
ngOnInit(): void {
this.mySrv.hello();
}
}
↑ Block 1
在開發過程中,很可能會用到 interface 來達到 ISP(Interface-Segregation Principle),而且也想要使用 interface 來注入相依的物件,但因為 TypeScript 的 interface 會在編譯後消失,不會產生任何的程式碼,也就導致 Angular 無法解析出正確的實體。
這時候如果想要取得一個實作 Interface 的物件,就可以透過 @Inject 這個 decorator 搭配 InjectionToken 來達成。
要使用 InjectionToken 與 @Inject 的方式取得物件,會需要達成幾個條件:
以 Block 1 的範例程式碼來說,我在另一個檔案宣告了 IMyService 介面、實作該介面的 MyService 類別以及要用來提供 DI 功能的 MY_SERVICE_TOKEN
InjectionToken 物件:
export interface IMyService {
hello(): void;
}
export class MyService implements IMyService {
hello(): void {
console.log('Hello, world.');
}
}
export const MY_SERVICE_TOKEN = new InjectionToken<IMyService>('IMyService token');
之後就可以拿著這個 MY_SERVICE_TOKEN
到處注入相依性了!
舉例來說我可以在 AppComopnent 的 @Component decorator 使用這個 token 來加入 IMyService 的 provider:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
providers: [
{
provide: MY_SERVICE_TOKEN,
useClass: MyService
}
],
})
export class AppComponent implements OnInit {
// ... 略
}
↑ Block 2
也可以在 AppModule 內加入 provider:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
bootstrap: [AppComponent],
providers: [
{
provide: MY_SERVICE_TOKEN,
useClass: MyService
}
],
})
export class AppModule { }
↑ Block 3
最終都可以在 AppComponent 內取得 MyService 的物件實體。
底下是 InjectionToken 這個類別的實作:
export class InjectionToken<T> {
/** @internal */
readonly ngMetadataName = 'InjectionToken';
readonly ɵprov: never|undefined;
constructor(protected _desc: string, options?: {
providedIn?: Type<any>|'root'|'platform'|'any'|null, factory: () => T
}) {
this.ɵprov = undefined;
if (typeof options == 'number') {
// This is a special hack to assign __NG_ELEMENT_ID__ to this instance.
// __NG_ELEMENT_ID__ is Used by Ivy to determine bloom filter id.
// We are using it to assign `-1` which is used to identify `Injector`.
(this as any).__NG_ELEMENT_ID__ = options;
} else if (options !== undefined) {
this.ɵprov = ɵɵdefineInjectable({
token: this,
providedIn: options.providedIn || 'root',
factory: options.factory,
});
}
}
toString(): string {
return `InjectionToken ${this._desc}`;
}
}
透過這個類別的 constructor,可以得知除了像先前的例子單純建立一個 token 以外,也可以像下面的程式碼區塊一樣,直接建立一個具有 Tree-shakeable 能力的 InjectionToken:
const MY_SVC_TOKEN = new InjectionToken<MySvc>('My Service', {
providedIn: 'root',
factory: () => new MySvc();
});
使用 InjectionToken 類別,我們能夠建立為 interface 建立 DI token,並搭配 @Inject decorator 來讓 Angular 的 DI 機制取得實體。這種方式很適合用在要提供的物件類型是 interface 或是其他經過編譯後會消失的類型上。
那麼,以上就是今天的 Tip!
明天會繼續來看透過 InjectionToken 建立 token 時可以做的其他事情、以及背後的運作原理。
以下按照入團順序列出我們團隊夥伴的系列文章?