本文由達觀數據研究院根據《Total Guide To Angular 6+ Dependency Injection — providedIn vs providers》編譯,若有不當,還請指正。前端
Angular 6爲咱們提供了更好的語法——provideIn,用於將服務註冊到Angular依賴注入機制中。架構
然而,新語法帶來了很是多使用上的困惑,在GitHub評論,Slack和Stack
Overflow上看到一些開發者常常混淆。因此,如今,讓咱們把這一切都說清楚。
依賴注入回顧(可選)dom
使用舊語法進行依賴注入—— providers: []ide
使用新語法進行依賴注入—— providedIn: 'root' | SomeModule函數
providedIn 的使用場景學習
在項目中如何使用新語法的最佳實踐ui
總結spa
讓咱們快速回顧一下依賴注入是什麼,若是感受簡單,你能夠跳過這一小節。3d
依賴注入(DI)是一種建立依賴其餘對象的方法。在建立一個新的對象實例時,依賴注入系統將會提供依賴對象(稱爲依賴關係) - Angular Docs
咱們的組件和服務都是類,每一個類都有一個名爲constructor的特殊函數,當咱們想要在咱們的應用程序中建立該類的對象(實例)時調用它。code
在咱們的服務中,咱們都看到過相似於 constructor(private http: HttpClient)這樣的代碼。假如沒有Angular DI機制,咱們必須手動提供HttpClient來建立咱們本身的服務。
咱們的代碼會像這樣:const myService = new MyService(httpClient);可是,咱們還須要得到httpClient對象。
因而,我須要再實例一個HttpClient:const httpClient = new HttpClient(httpHandler);但httpHandler又從哪來?若是這樣建立下去,到底何時是個頭。並且,這個過程至關繁瑣,並且很容易出錯。
幸虧,Angular的DI機制自動地幫咱們完成了上述的全部操做,咱們所要作的只是在組件的構造函數中指定依賴項,組件將會很輕鬆地就能用到這些依賴。可天下沒有免費的午飯...
爲了讓工程實踐作的更好,Angular必須瞭解咱們想要注入到組件和服務中的每個實體。
在Angular 6 發佈之前, 惟一的方法是在 providers: [] 中指定服務,以下:
根據具體使用場景, providers: [] 將有三種不一樣的用法:
一、在預加載的模塊的@NgModule裝飾器中指定 providers: []
二、在懶加載的模塊的@NgModule裝飾器中指定 providers: []
三、在@Component和@Directive裝飾器中指定 providers: []
在預加載模塊中使用providers: []
在這種狀況下,服務將是全局單例的。即便它被多個模塊的providers: []重複申明,它也不會從新建立實例。注入器只會建立一個實例,這是由於它們最終都會註冊到根級注入器。
在懶加載模塊中使用providers: []
在應用程序運行初始化後一段時間,懶加載模塊中提供的服務實例纔會在子注入器(懶加載模塊)上建立。若是在預加載模塊中注入這些服務,將會報 No provider for MyService! 錯誤。
在@Component和@Directive中使用providers: []
服務是按組件實例化的,而且能夠在組件及其子樹中的全部子組件中訪問。在這種狀況下,服務不是單例的,每次咱們在另外一個組件的模板中使用組件時,咱們都會得到所提供服務的新實例。 這也意味着服務實例將與組件一塊兒銷燬......
上面圖中,RandomService 在 RandomComponent中被註冊,所以,每當咱們在模板中使用<random> </ random>組件時,咱們將獲得不一樣的隨機數。
若是在模塊級別提供 RandomService而且將被做爲單例提供,則不會出現這種狀況。 在這種狀況下,<random> </ random> 組件的每次使用都會顯示相同的隨機數,由於該數字是在服務實例化期間生成的。
隨着Angular 6的出現,咱們可使用全新的語法在咱們的應用程序中創建依賴項, 官方名稱是「Tree-shakable providers」,咱們經過使用 @Injectable 裝飾器的新增的 provideIn 屬性來使用它。
咱們能夠將provideIn視爲以反向方式指定依賴關係。 如今不是模塊申明須要哪些服務,而是服務自己宣佈它應該提供給哪些模塊使用
申明的模塊能夠是 root 或其餘任何可用模塊。另外,root 其實是 AppModule 的別名,這是一個很好的語法糖,咱們所以不須要額外導入 AppModule。
新語法很是簡單,如今讓咱們實踐一下,來探索在應用程序開發過程當中可能遇到的一些有趣場景......
在大多數狀況下,這是對咱們有用的最多見的解決方案。此解決方案的主要好處是,只有真正「使用」這些服務時纔會打包服務代碼。 「使用」表明注入某些組件或其餘服務。
另外一方面,providedIn: 'root' 在代碼可複用方面爲開發人員帶來了巨大的積極影響。
在 providedIn
出現以前,須要在主模塊的 providers: []
中注入全部公共服務。而後,組件須要導入該模塊,這將致使全部(可能的大量)的服務導入進該組件,即便咱們只想使用其中一個服務。
如今,providedIn: 'root'解決了這個問題,咱們不須要在模塊中導入這些服務,咱們要作的僅僅是使用它們。
若是咱們在懶加載中使用 providedIn: 'root' 來實現服務會發生什麼?
從技術上講,'root'表明 AppModule ,但Angular足夠聰明,若是該服務只是在惰性組件/服務中注入,那麼它只會綁定在延遲加載的bundle中。
若是咱們又額外將服務注入到其餘正常加載的模塊中,那麼該服務會自動綁定到 mian 的bundle中。
簡單來說:
一、若是服務僅被注入到懶加載模塊,它將捆綁在懶加載包中
二、若是服務又被注入到正常模塊中,它將捆綁在主包中
這種行爲的問題在於,在擁有大量模塊和數百項服務的大型應用程序中,它可能變得很是不可預測。
幸運的是,有一種方法能夠防止這種狀況的發生,咱們將在下面的章節中探討如何增強模塊的邊界。
這個解決方案一般沒有意義,咱們應該堅持使用 provideIn:'root'。
它可用於防止應用程序的其他部分注入服務而無需導入相應的模塊,但這其實並非必需的。
附註 - 延遲加載模塊的多重好處
Angular最大的優勢之一是咱們能夠很是容易的將應用程序分紅徹底獨立的邏輯塊,這有如下好處…
一、更小的初始化代碼,這意味着更快的加載和啓動時間
二、懶惰加載的模塊是真正隔離的。主機應用程序應該引用它們的惟一一點是某些路由的 loadChildren 屬性。
這意味着,若是使用正確,能夠將整個模塊刪除或外部化爲獨立的應用程序/庫。可能有數百個組件和服務的模塊能夠在不影響應用程序其他部分的狀況下隨意移動,這是很是使人驚奇的!
這種隔離的另外一個巨大好處是,對懶惰模塊的邏輯進行更改永遠不會致使應用程序的其餘部分出錯。
這個解決方案很是棒,由於它能夠幫助咱們防止在所需模塊以外使用咱們的服務。在開發大型應用程序時,保持依賴關係圖是很是有必要的,由於無約束的無處不在的注入可能會致使沒法解決的巨大混亂!
不幸的是,有一個小問題……循環依賴
幸運的是,咱們能夠經過建立一個 LazyServiceModule 來避免這個問題,它將是 LazyModule 的一個子模塊,並將被用做咱們想要提供的全部懶加載服務的「錨」。以下圖所示:
雖然有點不方便,但咱們只需增長一個模塊,這種方法結合了二者的優勢:
- 它防止咱們將懶加載的服務注入應用程序的正常加載模塊
- 只有當服務被真正注入其餘惰性組件時,它纔會打包到服務中
新語法能在 @Component和 @Directive中使用嗎?
不,它們並不能。
咱們仍然須要在 @Component 或 @Directive 中使用 provider:[]來建立多個服務實例(每一個組件)。 目前尚未辦法解決這個問題......
庫
當處理開發庫、實用程序或任何其餘形式的可重用 Angular 邏輯時,providedIn: 'root'是很是好的解決方案。
當消費者應用程序只須要可用庫功能的一個子集時,它也處理的很是好。只有真正使用的東西纔會打包進咱們的應用程序中,咱們都但願打包出來的文件越小越好。
懶加載模塊
使用 providedIn: LazyServicesModule,而後由 LazyModule 導入,再由 Angular 路由器惰性加載,以實施嚴格的模塊邊界和可維護的架構!
這種方法能夠防止咱們將懶加載的服務注入應用程序的正常加載模塊
使用providedIn: 'root' , 'root'將會正常工做,服務也會被正確捆綁,可是使用 providedIn: LazyServiceModule 爲咱們提供了早期的「missing provider」錯誤,這是一個很好的早期信號,這有助於咱們從新思考咱們的架構。
何時使用老的 providers:[] 語法?
咱們須要將配置傳遞給咱們的服務嗎?
或者換句話說,咱們是否有一個使用 SomeModule.forRoot(someConfig) 解決的場景?
在這種狀況下,咱們仍然須要使用 providers: [],由於新的語法無助於咱們定製服務。
另外一方面,若是咱們曾經使用 SomeModule.forRoot() 來阻止延遲加載模塊建立服務的其餘實例,咱們能夠簡單地使用 providedIn: 'root' 來實現這一點。
將 providedIn: 'root'用於在整個應用程序中做爲單例可用的服務;
永遠不要使用 providedIn:EagerLiymportedmodule,您不須要它,若是有一些很是特殊的用例,那麼請使用 providers: [] 來代替;
使用 providedIn: LazyServiceModule來防止咱們將懶加載的服務注入應用程序的正常加載模塊;
若是咱們想使用 LazyServiceModule,那麼咱們必須將其導入 LazyModule,以防止循環依賴警告。而後,LazyModule將以標準方式使用 Angular Router 爲某些路由進行懶加載。
使用 @Component 或 @Directive 內部的 providers: [],爲特定的組件子樹提供服務,這也將致使建立多個服務實例(每一個組件使用一個服務實例)
始終嘗試保守地肯定您的服務範圍,以防止依賴蔓延和由此產生的巨大混亂!
關於譯者王玉略:達觀數據前端開發工程師,負責達觀數據前端開發,喜歡探索新技術,致力於將代碼與平常生活相結合,提升生活效率。