Angular 中有兩個注入器層次結構:
(1) ModuleInjector 層次結構 —— 使用 @NgModule() 或 @Injectable() 注解在此層次結構中配置 ModuleInjector。
(2) ElementInjector 層次結構 —— 在每個 DOM 元素上隐式建立。除非你在 @Directive() 或 @Component() 的 providers 屬性中進行配置,否則預設情況下,ElementInjector 為空。
意思是,隻要我們在 @NgModule 裡通過 providers 數組定義服務提供者,以及在服務實作類裡使用 @Injectable 注解,我們實際上就在定義 ModuleInjector.
ModuleInjector
可以通過以下兩種方式之一配置 ModuleInjector :
使用 @Injectable() 的 providedIn 屬性引用 @NgModule() 或 root。
使用 @NgModule() 的 providers 數組。
使用 @Injectable() 的 providedIn 屬性優于 @NgModule() 的 providers 數組,因為使用 @Injectable() 的 providedIn 時,優化工具可以進行 tree shaking,進而删除你的應用程式中未使用的服務,以減小捆綁包尺寸。
下面是通過 NgModule.providers 定義 ModuleInjector 注釋:

@usageNotes — Dependencies whose providers are listed here become available for injection into any component, directive, pipe or service that is a child of this injector. The NgModule used for bootstrapping uses the root injector, and can provide dependencies to any part of the app.
A lazy-loaded module has its own injector, typically a child of the app root injector. Lazy-loaded services are scoped to the lazy-loaded module’s injector. If a lazy-loaded module also provides the UserService, any component created within that module’s context (such as by router navigation) gets the local instance of the service, not the instance in the root injector. Components in external modules continue to receive the instance provided by their injectors.
子 ModuleInjector 是在惰性加載其它 @NgModules 時建立的。
使用 @Injectable() 的 providedIn 屬性提供服務的方式如下:
@Injectable() 裝飾器辨別服務類。該 providedIn 屬性配置指定的 ModuleInjector,這裡的 root 會把讓該服務在 root ModuleInjector 上可用。
平台注入器
在 root 之上還有兩個注入器,一個是額外的 ModuleInjector,一個是 NullInjector()。
思考下 Angular 要如何通過 main.ts 中的如下代碼引導應用程式:
bootstrapModule() 方法會建立一個由 AppModule 配置的注入器作為平台注入器的子注入器。也就是 root ModuleInjector。
platformBrowserDynamic() 方法建立一個由 PlatformModule 配置的注入器,該注入器包含特定平台的依賴項。這允許多個應用共享同一套平台配置。例如,無論你運作多少個應用程式,浏覽器都隻有一個 URL 欄。你可以使用 platformBrowser() 函數提供 extraProviders,進而在平台級别配置特定平台的額外提供者。
層次結構中的下一個父注入器是 NullInjector(),它是樹的頂部。如果你在樹中向上走了很遠,以至于要在 NullInjector() 中尋找服務,那麼除非使用 @Optional(),否則将收到錯誤消息,因為最終所有東西都将以 NullInjector() 結束并傳回錯誤,或者對于 @Optional(),傳回 null。
NullInjector 相當于注入器機制的錯誤處理,default 機制。
下圖展示了前面各段落描述的 root ModuleInjector 及其父注入器之間的關系。
ElementInjector
Angular 會為每個 DOM 元素隐式建立 ElementInjector。
可以用 @Component() 裝飾器中的 providers 或 viewProviders 屬性來配置 ElementInjector 以提供服務。例如,下面的 TestComponent 通過提供此服務來配置 ElementInjector:
這地方有點費解,關 DOM 什麼事?
@Directive() 和 @Component()
元件是一種特殊類型的指令,這意味着 @Directive() 具有 providers 屬性,@Component() 也同樣如此。 這意味着指令群組件都可以使用 providers 屬性來配置提供者。當使用 providers 屬性為元件或指令配置提供者時,該提供程商就屬于該元件或指令的 ElementInjector。同一進制素上的元件和指令共享同一個注入器。
解析規則
當為元件/指令解析令牌時,Angular 分為兩個階段來解析它:
針對 ElementInjector 層次結構(其父級)
針對 ModuleInjector 層次結構(其父級)
當元件聲明依賴項時,Angular 會嘗試使用它自己的 ElementInjector 來滿足該依賴。 如果元件的注入器缺少提供者,它将把請求傳給其父元件的 ElementInjector。
這些請求将繼續轉發,直到 Angular 找到可以處理該請求的注入器或用完祖先 ElementInjector。
如果 Angular 在任何 ElementInjector 中都找不到提供者,它将傳回到發起請求的元素,并在 ModuleInjector 層次結構中進行查找。如果 Angular 仍然找不到提供者,它将引發錯誤。
如果你已在不同級别注冊了相同 DI 令牌的提供者,則 Angular 會用遇到的第一個來解析該依賴。例如,如果提供者已經在需要此服務的元件中本地注冊了,則 Angular 不會再尋找同一服務的其它提供者。
解析修飾符
解析修飾符分為三類:
如果 Angular 找不到你要的東西該怎麼辦,用 @Optional()
從哪裡開始尋找,用 @SkipSelf()
到哪裡停止尋找,用 @Host() 和 @Self()
預設情況下,Angular 始終從目前的 Injector 開始,并一直向上搜尋。修飾符使你可以更改開始(預設是自己)或結束位置。
@Optional()
@Optional() 允許 Angular 将你注入的服務視為可選服務。這樣,如果無法在運作時解析它,Angular 隻會将服務解析為 null,而不會抛出錯誤。在下面的範例中,服務 OptionalService 沒有在 @NgModule() 或元件類中提供,是以它沒有在應用中的任何地方。
@Self()
使用 @Self() 讓 Angular 僅檢視目前元件或指令的 ElementInjector。
@Self() 的一個好例子是要注入某個服務,但隻有當該服務在目前宿主元素上可用時才行。為了避免這種情況下出錯,請将 @Self() 與 @Optional() 結合使用。
ElementInjector 用例範例
場景:服務隔離
出于架構方面的考慮,可能會讓你決定把一個服務限制到隻能在它所屬的那個應用域中通路。 比如,這個例子中包括一個用于顯示反派清單的 VillainsListComponent,它會從 VillainsService 中獲得反派清單資料。
如果你在根子產品 AppModule 中(也就是你注冊 HeroesService 的地方)提供 VillainsService,就會讓應用中的任何地方都能通路到 VillainsService,包括針對英雄的工作流。如果你稍後修改了 VillainsService,就可能破壞了英雄元件中的某些地方。在根子產品 AppModule 中提供該服務将會引入此風險。
該怎麼做呢?你可以在 VillainsListComponent 的 providers 中繼資料中提供 VillainsService,就像這樣:
在 VillainsListComponent 的中繼資料中而不是其它地方提供 VillainsService 服務,該服務就會隻在 VillainsListComponent 及其子元件樹中可用。
VillainService 對于 VillainsListComponent 來說是單例的,因為它就是在這裡聲明的。隻要 VillainsListComponent 沒有銷毀,它就始終是 VillainService 的同一個執行個體。但是對于 VillainsListComponent 的多個執行個體,每個 VillainsListComponent 的執行個體都會有自己的 VillainService 執行個體。
每個元件的執行個體都有它自己的注入器。 在元件級提供服務可以確定元件的每個執行個體都得到一個自己的、私有的服務執行個體。
場景:專門的提供者
這就是 SAP Spartacus 典型的應用場景。
在其它層級重新提供服務的另一個理由,是在元件樹的深層中把該服務替換為一個更專門化的實作。
代碼第13行的 BudgetCostCenterListService 是 ListService 的一個具體實作。