Angular 項目中的可搖樹依賴
Tree-shakable dependencies in Angular projects
Tree-shakable 依賴更容易推理和編譯成更小的包。
Angular 模塊 (NgModules) 曾經是提供應用程序范圍依賴項(例如常量、配置、函數和基于類的服務)的主要方式。 從 Angular 版本 6 開始,我們可以創建可搖樹的依賴項,甚至可以忽略 Angular 模塊。
Angular module providers create hard dependencies
當我們使用 NgModule 裝飾器工廠的 providers 選項提供依賴項時,Angular 模塊文件頂部的 import 語句引用了依賴項文件。
這意味著 Angular 模塊中提供的所有服務都成為包的一部分,即使是那些不被 declarable 或其他依賴項使用的服務。 讓我們稱這些為硬依賴,因為它們不能被我們的構建過程搖樹。
相反,我們可以通過讓依賴文件引用 Angular 模塊文件來反轉依賴關系。 這意味著即使應用程序導入了 Angular 模塊,它也不會引用依賴項,直到它在例如組件中使用依賴項。
Providing singleton services
許多基于類的服務被稱為應用程序范圍的單例服務——或者簡稱為單例服務,因為我們很少在平臺注入器級別使用它們。
Pre-Angular 6 singleton service providers
在 Angular 版本 2 到 5 中,我們必須向 NgModule 的 providers 選項添加單例服務。 然后我們必須注意,只有急切加載的 Angular 模塊才會導入提供的 Angular 模塊——按照慣例,這是我們應用程序的 CoreModule。
// pre-six-singleton.service.ts import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; @Injectable() export class PreSixSingletonService { constructor(private http: HttpClient) {} }
// pre-six.module.ts import { NgModule } from '@angular/core'; import { PreSixSingletonService } from './pre-six-singleton.service'; @NgModule({ providers: [PreSixSingletonService], }) export class PreSixModule {}
// core.module.ts import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { PreSixModule } from './pre-six.module.ts'; @NgModule({ imports: [HttpClientModule, PreSixModule], }) export class CoreModule {}
以上是 Pre-Angular 6 singleton service.
如果我們在延遲加載的功能模塊中導入提供 Angular 的模塊,我們將獲得不同的服務實例。
Providing services in mixed Angular modules
當在帶有可聲明的 Angular 模塊中提供服務時,我們應該使用 forRoot 模式來表明它是一個混合的 Angular 模塊——它同時提供了可聲明和依賴項。
這很重要,因為在延遲加載的 Angular 模塊中導入具有依賴項提供程序的 Angular 模塊將為該模塊注入器創建新的服務實例。 即使已經在根模塊注入器中創建了一個實例,也會發生這種情況。
// pre-six-mixed.module.ts import { ModuleWithProviders, NgModule } from '@angular/core'; import { MyComponent } from './my.component'; import { PreSixSingletonService } from './pre-six-singleton.service'; @NgModule({ declarations: [MyComponent], exports: [MyComponent], }) export class PreSixMixedModule { static forRoot(): ModuleWithProviders { return { ngModule: PreSixMixedModule, providers: [PreSixSingletonService], }; } }
以上是 The forRoot pattern for singleton services.
靜態 forRoot 方法用于我們的 CoreModule,它成為根模塊注入器的一部分。
Tree-shakable singleton service providers
幸運的是,Angular 6 向 Injectable 裝飾器工廠添加了 providedIn 選項。 這是聲明應用程序范圍的單例服務的一種更簡單的方法。
// modern-singleton.service.ts import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class ModernSingletonService { constructor(private http: HttpClient) {} }
以上是 Modern singleton service.
單例服務是在第一次構建依賴它的任何組件時創建的。
始終使用 Injectable 裝飾基于類的服務被認為是最佳實踐。 它配置 Angular 以通過服務構造函數注入依賴項。
在 Angular 版本 6 之前,如果我們的服務沒有依賴項,則 Injectable 裝飾器在技術上是不必要的。 盡管如此,添加它仍然被認為是最佳實踐,以便我們在以后添加依賴項時不會忘記這樣做。
現在我們有了 providedIn 選項,我們還有另一個理由總是將 Injectable 裝飾器添加到我們的單例服務中。
這個經驗法則的一個例外是,如果我們創建的服務總是打算由工廠提供者構建(使用 useFactory 選項)。 如果是這種情況,我們不應指示 Angular 將依賴項注入其構造函數。
providedIn: ‘root’
該選項將在根模塊注入器中提供單例服務。 這是為引導的 Angular 模塊創建的注入器——按照慣例是 AppModule.事實上,這個注入器用于所有急切加載的 Angular 模塊。
或者,我們可以將 providedIn 選項引用到一個 Angular 模塊,這類似于我們過去對混合 Angular 模塊使用 forRoot 模式所做的事情,但有一些例外。
// modern-singleton.service.ts import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { ModernMixedModule } from './modern-mixed.module'; @Injectable({ providedIn: ModernMixedModule, }) export class ModernSingletonService { constructor(private http: HttpClient) {} }
// modern-mixed.module.ts import { NgModule } from '@angular/core'; import { MyComponent } from './my.component'; @NgModule({ declarations: [MyComponent], exports: [MyComponent], }) export class ModernMixedModule {}
單例服務的現代 forRoot 替代方案。
與 ‘root’ 選項值相比,使用此方法有兩個不同之處:
除非已導入提供的 Angular 模塊,否則無法注入單例服務。
由于單獨的模塊注入器,延遲加載的 Angular 模塊和 AppModule 會創建自己的實例。
Providing primitive values
假設我們的任務是向 Internet Explorer 11 用戶顯示棄用通知。 我們將創建一個 InjectionToken
這允許我們將布爾標志注入服務、組件等。 同時,我們只對每個模塊注入器評估一次 Internet Explorer 11 檢測表達式。 這意味著根模塊注入器一次,延遲加載模塊注入器一次。
在 Angular 版本 4 和 5 中,我們必須使用 Angular 模塊為注入令牌提供值。
首先新建一個 token 實例:
// is-internet-explorer.token.ts import { InjectionToken } from '@angular/core'; export const isInternetExplorer11Token: InjectionToken
然后新建一個 module,通過 factory 為該 token 指定運行時應該注入什么樣的值:
// internet-explorer.module.ts import { NgModule } from '@angular/core'; import { isInternetExplorer11Token } from './is-internet-explorer-11.token'; @NgModule({ providers: [ { provide: isInternetExplorer11Token, useFactory: (): boolean => /Trident\/7\.0.+rv:11\.0/.test(navigator.userAgent), }, ], }) export class InternetExplorerModule {}
以上是:Angular 4–5 dependency injection token with factory provider.
Angular 6 的改進:
從 Angular 版本 6 開始,我們可以將工廠傳遞給 InjectionToken 構造函數,從而不再需要 Angular 模塊。
// is-internet-explorer-11.token.ts import { InjectionToken } from '@angular/core'; export const isInternetExplorer11Token: InjectionToken
使用工廠提供程序時,providedIn 默認為“root”,但讓我們通過保留它來明確。 它也與使用 Injectable 裝飾器工廠聲明提供者的方式更加一致。
Value factories with dependencies
我們決定將 user agent 字符串提取到它自己的依賴注入令牌中,我們可以在多個地方使用它,并且每個模塊注入器只從瀏覽器讀取一次。
在 Angular 版本 4 和 5 中,我們必須使用 deps 選項(依賴項的縮寫)來聲明工廠依賴項。
// user-agent.token.ts import { InjectionToken } from '@angular/core'; export const userAgentToken: InjectionToken
// is-internet-explorer.token.ts import { InjectionToken } from '@angular/core'; export const isInternetExplorer11Token: InjectionToken
// internet-explorer.module.ts,在一個 module 里同時提供兩個 token 的值 import { Inject, NgModule } from '@angular/core'; import { isInternetExplorer11Token } from './is-internet-explorer.token'; import { userAgentToken } from './user-agent.token'; @NgModule({ providers: [ { provide: userAgentToken, useFactory: () => navigator.userAgent }, { deps: [[new Inject(userAgentToken)]], provide: isInternetExplorer11Token, useFactory: (userAgent: string): boolean => /Trident\/7\.0.+rv:11\.0/.test(userAgent), }, ], }) export class InternetExplorerModule {}
不幸的是,依賴注入令牌構造函數目前不允許我們聲明工廠提供程序依賴項。 相反,我們必須使用來自@angular/core 的注入函數。
// user-agent.token.ts import { InjectionToken } from '@angular/core'; export const userAgentToken: InjectionToken
// is-internet-explorer-11.token.ts import { inject, InjectionToken } from '@angular/core'; import { userAgentToken } from './user-agent.token'; export const isInternetExplorer11Token: InjectionToken
以上是 Angular 6 之后,如何實例化具有依賴關系的 injection token 的代碼示例。
注入函數從提供它的模塊注入器中注入依賴項——在這個例子中是根模塊注入器。 它可以被 tree-shakable 提供者中的工廠使用。 Tree-shakable 基于類的服務也可以在它們的構造函數和屬性初始化器中使用它。
Providing platform-specific APIs
為了利用特定于平臺的 API 并確保高水平的可測試性,我們可以使用依賴注入令牌來提供 API。
讓我們看一個 Location 的例子。 在瀏覽器中,它可用作全局變量 location,另外在 document.location 中。 它在 TypeScript 中具有 Location 類型。 如果你在你的一個服務中通過類型注入它,你可能沒有意識到 Location 是一個接口。
接口是 TypeScript 中的編譯時工件,Angular 無法將其用作依賴注入令牌。 Angular 在運行時解決依賴關系,因此我們必須使用在運行時可用的軟件工件。 很像 Map 或 WeakMap 的鍵。
相反,我們創建了一個依賴注入令牌并使用它來將 Location 注入到例如服務中。
// location.token.ts import { InjectionToken } from '@angular/core'; export const locationToken: InjectionToken
// browser.module.ts import { NgModule } from '@angular/core'; import { locationToken } from './location.token'; @NgModule({ providers: [{ provide: locationToken, useFactory: (): Location => document.location }], }) export class BrowserModule {}
以上是 Angular 4 - 5 的老式寫法。
Angular 6 的新式寫法:
// location.token.ts import { InjectionToken } from '@angular/core'; export const locationToken: InjectionToken
在 API 工廠中,我們使用全局變量 document. 這是在工廠中解析 Location API 的依賴項。 我們可以創建另一個依賴注入令牌,但事實證明 Angular 已經為這個特定于平臺的 API 公開了一個——由@angular/common 包導出的 DOCUMENT 依賴注入令牌。
在 Angular 版本 4 和 5 中,我們將通過將其添加到 deps 選項來聲明工廠提供程序中的依賴項。
// location.token.ts import { InjectionToken } from '@angular/core'; export const locationToken: InjectionToken
// browser.module.ts import { DOCUMENT } from '@angular/common'; import { Inject, NgModule } from '@angular/core'; import { locationToken } from './location.token'; @NgModule({ providers: [ { deps: [[new Inject(DOCUMENT)]], provide: locationToken, useFactory: (document: Document): Location => document.location, }, ], }) export class BrowserModule {}
下面是新式寫法:
和以前一樣,我們可以通過將工廠傳遞給依賴注入令牌構造函數來擺脫 Angular 模塊。 請記住,我們必須將工廠依賴項轉換為對注入的調用。
// location.token.ts import { DOCUMENT } from '@angular/common'; import { inject, InjectionToken } from '@angular/core'; export const locationToken: InjectionToken
現在我們有了一種為特定于平臺的 API 創建通用訪問器的方法。 這在測試依賴它們的 declarable 和服務時將證明是有用的。
Testing tree-shakable dependencies
在測試 tree-shakable 依賴項時,重要的是要注意依賴項默認由工廠提供,作為選項傳遞給 Injectable 和 InjectionToken。
為了覆蓋可搖樹依賴,我們使用 TestBed.overrideProvider,例如 TestBed.overrideProvider(userAgentToken, { useValue: ‘TestBrowser’ })。
Angular 模塊中的提供者僅在將 Angular 模塊添加到 Angular 測試模塊導入時才用于測試,例如 TestBed.configureTestingModule({imports: [InternetExplorerModule] })。
Do tree-shakable dependencies matter?
Tree-shakable 依賴對于小型應用程序沒有多大意義,我們應該能夠很容易地判斷一個服務是否在實際使用中。
相反,假設我們創建了一個供多個應用程序使用的共享服務庫。 應用程序包現在可以忽略在該特定應用程序中未使用的服務。 這對于具有共享庫的 monorepo 工作區和 multirepo 項目都很有用。
Tree-shakable 依賴項對于 Angular 庫也很重要。 例如,假設我們在應用程序中導入了所有 Angular Material 模塊,但僅使用了部分組件及其相關的基于類的服務。 因為 Angular Material 提供了搖樹服務,所以我們的應用程序包中只包含我們使用的服務。
Summary
我們已經研究了使用 tree-shakable 提供程序配置注入器的現代選項。 與前 Angular 6 時代的提供者相比,可搖動樹的依賴項通常更容易推理且不易出錯。
來自共享庫和 Angular 庫的未使用的 tree-shakable 服務在編譯時被刪除,從而產生更小的包。
Angular web前端
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。