iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 4
2
Modern Web

從巨人的 Tip 看 Angular系列 第 4

[Day 4] @Inject(token: InjectionToken<T>)!

  • 分享至 

  • xImage
  •  

前幾天都在分享 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 的方式取得物件,會需要達成幾個條件:

  1. 建立 token。
  2. 加入 provider。

以 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 的物件實體。

一言不合就爬 code

底下是 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 時可以做的其他事情、以及背後的運作原理。

為團隊加油!

以下按照入團順序列出我們團隊夥伴的系列文章?


上一篇
[Day 3] 深度探討在 Component 內 inject Component 的解析流程
下一篇
[Day 5] 在建立 InjectionToken 的時候加入 Dependency
系列文
從巨人的 Tip 看 Angular30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言