關於 Angular 的 Injector ,雖然我之前在寫「Angular 深入淺出三十天」的系列文時,就有在基礎結構說明(四)這篇文章裡介紹過,不過今天我在跟朋友們聊到相關的話題時,除了好好的做了個實驗,並把程式碼保留下來之外,也忽然起了個念頭,覺得是該把這個問題寫成文章,讓剛學 Angular 或是對 Angular 的了解沒那麼深的朋友們可以比較容易理解、比較不會用錯(就我個人的經驗來看,沒有注意到這個部分的朋友們還滿多的)。
問題描述
在 Angular 裡寫 Service 的時候,我們一般會用以下三種方式註冊它:
- 在該 Service 的
@Injectable()
裡加上{ providedIn: 'root' }
的 Metadata ( Angular 7+ 推薦寫法),抑或者在 AppModule 的providers
裡註冊它( Angular 6- 的寫法)。 - 在相關的功能模組的
providers
裡註冊它。 - 在某個 Component 的
providers
裡註冊它。
其實在 Angular 9 之後的版本,除了
providedIn: 'root'
之外,還多了'platform'
與'any'
這兩個選擇,不過此篇文章不討論這個部分。
這三種註冊方式其中的第一種與第三種,大家比較不會有問題,比較會有問題的是第二種,很多人可能會覺得,我使用起來明明就是同一個實體呀?怎麼官方說是不同實體呢?
實驗開始
為了釐清這個問題,我做了個實驗,程式碼在這裡:https://stackblitz.com/edit/angular-ivy-5fdo5y?file=src/app/app.component.html
在這個實驗裡,我用了四個 Module ─ AboutModule
、ContactModule
、HomeModule
、WhateverModule
與兩個 Service ─ ASservice
與 BService
來模擬大部分會遇到的情況:
AService
使用providedIn: 'root'
註冊。HomeModule
、ContactModule
、WhateverModule
都 import 到 AppModule 裡。AboutModule
用 LazyLoading 的方式載入。BService
則分別註冊在ContactModule
、HomeModule
、AboutModule
的providers
裡與WhateverComponent
的providers
裡。
結果:
有看懂嗎? BService
在 HomeComponent
與 ContactComponet
裡是用同一個實體,這是因為他們都有被 import 到 AppModule 裡,這時 AppModule(上層 Module)會接手 ContactModule
、HomeModule
(子層 Module)的 injector,所以他們雖然有分別註冊 BService
,但實際上還是共用了同一個實體。
這部份其實官網文件有寫到:
網址在這:https://angular.io/guide/hierarchical-dependency-injection#moduleinjector
不過我這裡只有實驗一層的情況,如果 Module 一層一層的接下去,是不是也是一樣的狀況我就沒有特別做實驗了,大家可以自己玩看看。
至於 AboutComponent
跟 WhateverComponent
呢,很明顯都是使用不同的
BService
的實體,前者因為使用 LazyLoading 的關係,跟 AppModule 沒有直接關聯,後者則是因為是使用了第三種註冊方式的關係。
結論
總而言之,官方之所以提供了不同的註冊方式給我們,就是希望我們可以在不同的情況下使用不同的註冊方式來解決我們的問題,只要能夠熟知這其中的差異,就可以寫出好維護又高品質的系統囉!