什么是 Angular 的多级注入器
官方文檔
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 中的如下代碼引導應用程序:
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {...})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() 或組件類中提供,所以它沒有在應用中的任何地方。
export class OptionalComponent {constructor(@Optional() public optional?: OptionalService) {} }@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 的一個具體實現。
更多Jerry的原創文章,盡在:“汪子熙”:
總結
以上是生活随笔為你收集整理的什么是 Angular 的多级注入器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 几款流行文章编辑器性能评测报告(转)
- 下一篇: 写给即将离开校园准备进入 SAP 研究院