理解 Angular 依賴注入

發表於2024-02-11

之前只知道依賴注入是Angular中的一個特性,對於依賴注入有一個大概的瞭解,但是並沒有仔細查詢過依賴注入,這裡記錄一下對依賴注入的重新學習。

什麼是依賴注入:

當你開發系統的某個較小部件時(例如模組或類),你可能需要使用來自其他類的特性。例如,你可能需要 HTTP 服務來進行後端呼叫。

依賴注入或 DI 是一種設計模式和機制,用於建立應用程式的某些部分並將其傳遞到需要它們的應用程式的其他部分。Angular 支援這種設計模式,你可以在應用程式中使用它來提高靈活性和模組化程度。

從上面可以得出,依賴注入涉及到依賴提供者與依賴使用者,在依賴提供者註冊之後就可以在系統的其他地方使用依賴了。
在Angular中,提供的依賴通常是服務,當然也可以是其他內容。在註冊後,依賴會被注入到注入器內,然後就可以在系統的其他地方使用了,注入依賴項的最常見方法是在類的建構函式中宣告它。當 Angular 建立元件、指令或管道類的新例項時,它會透過檢視建構函式的引數型別來確定該類需要哪些服務或其他依賴項。

注入器是在應用程式啟動期間自動建立的,根注入器只有根模組中一個,然後每個模組中另外有屬於自己的注入器。(當然注入器也不只存在於模組)

image.png

image.png

使用中的性質

只可以在被Angular管理的類中使用

Angular DI中,允許帶有 Angular 裝飾器的類(例如元件、指令、管道和可注入物件)配置它們所需的依賴項。

也就是允許被Angular管理的類配置他們所需要的依賴項,而在其他的工具類中,則無法使用Angular的DI。

當有人請求依賴項時,注入器會檢查其登錄檔以檢視那裡是否已有可用的例項。如果沒有,就會建立一個新例項並將其儲存在登錄檔中。

下面結合程式碼簡單演示:

import {Injectable} from "@angular/core";

@Injectable()
export class AppService {
  title = 'AppService';
}
constructor(appService: AppService) {
  console.log(appService.title);
}

首先定義了一個AppService,並且在根模組的AppComponent元件中請求注入一個AppService例項,此時根注入器發現登錄檔中沒有可用例項,就嘗試使用AppService型別來建立一個新例項,但是發現在根模組中並沒有提供應該如何去例項化的類,所以就會報錯。此時將AppService新增到根模組的providers中即可。

結果報錯:
image.png

作用範圍

首先需要明確模組中定義的依賴提供者(providers)可以在本模組注入,也可以在其子模組中注入。

在 NgModule 級別,要使用 @NgModule 裝飾器的 providers 欄位。在這種情況下,AppService 可用於此 NgModule 或與本模組位於同一個 ModuleInjector 的其它模組中宣告的所有元件、指令和管道。

比如,模組關係如圖:
image.png
此時在B模組中注入了一個AppService,就可以在AppModule以及AModule中使用,AppModule和A、B兩個模組的關係:在AppModule中import A和B兩個模組。

對比引用內容也就是,AppService可以用於此NgModule(BModule),或與本模組位於同一個 ModuleInjector(RootModuleInjector) 的其他模組(AModule)中宣告的所有元件(AComponent, BComponent),指令和管道。

可以理解成在根模組中引入了A和B兩個模組,那麼A和B兩個模組中的providers都可以在根模組中使用,也就是根模組的providers = AProviders ∪ BProviders。所以在子模組中(AModule)也就可以使用根模組的依賴提供者。

搖樹最佳化

在 @Injectable 後設資料中註冊提供者還允許 Angular 透過從已編譯的應用程式中刪除沒用到的服務來最佳化應用程式,這個過程稱為搖樹最佳化(tree-shaking)。

這裡的後設資料是@Injectable中提供的資料,註冊提供者之後,如果某個依賴提供者沒有被使用,那麼注入器會將這些依賴提供者刪除來進行最佳化。

依賴注入配置

依賴注入需要為providers賦值,如providers: [AppService],可以出現在模組,服務,元件等Angular 裝飾器中。
常見的方式是:
providers: [AppService],這其實是一種簡寫,擴充套件之後就是:
providers: [{provide: AppService, useClass: AppService}],當提供者令牌為服務類時,注入器的預設行為是使用 new 運算子例項化該類。
{provide: AppService, useClass: AppService}是Provide介面的一個實現,有關該介面的內容:
其中第一個欄位provide:屬性包含一個令牌,該令牌會作為定位依賴值和配置注入器時的鍵。(值為"提供者令牌",可以是服務類,也可以是InjectionToken)
第二個欄位的值叫做提供者定義物件,用來告訴注入器如何建立依賴值;對於他的鍵,可以不只是useClass,可選項有:

  • useClass
  • useExisting
  • useFactory
  • useValue

useClass(類提供者)

這個提供者鍵名能讓你建立並返回指定類的新例項。
用法:

providers: [{provide: AppService, useClass: AppService1}]
providers: [{provide: AppService, useClass: AppService2}]

useExisting(別名提供者)

useExisting 提供者鍵允許你將一個令牌對映到另一個。
即對一個例項提供兩種訪問方式。
比如:

providers: [
    AppService
    {provide: IndexService, useExisting: AppService}
]

這樣在注入依賴的時候可以使用

constructor(service: AppService) {
}


constructor(service: IndexService) {
}

這兩種方式,最終訪問的都是一個例項。

useFactory(工廠提供者)

useFactory 提供者鍵允許你透過呼叫工廠函式來建立依賴物件。

使用這種方法,你可以根據 DI 和應用程式中其他地方的可用資訊建立動態值。

可見官方示例,最終程式碼:

providers: [
  Logger,
  UserService,
  { provide: HeroService,
    useFactory: heroServiceFactory,
    deps: [Logger, UserService]
  }
]

DI也就是指其中的deps的內容,heroServiceFactory是一個(logger: Logger, userService: UserService) => HeroService,其透過userService中的內容來動態的例項化HeroService。

useValue(值提供者)

useValue 鍵允許你將固定值與某個 DI 令牌相關聯

InjectionToken 物件

可以定義和使用一個 InjectionToken 物件來為非類的依賴(函式、物件、基本型別[例如字串或 Boolean]或任何其他型別)選擇一個提供者令牌。

// 定義提供者令牌
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

// 依賴提供
providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]

// 依賴使用
constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

new InjectionToken<AppConfig>('app.config')中的AppConfig表示依賴提供者的型別,'app.config'是對提供者令牌的描述,兩者用來指明此令牌的用途。
這裡的AppConfig是一個介面,Ts是可以使用介面作為提供者令牌的,但是由於程式在執行時使用的是js,所以沒有可供 DI 框架使用的執行時表示或令牌。所以下面這種寫法是不可取的

[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]

constructor(private config: AppConfig){ }