Tree Shakeable Providers and Services in Angular
Angular 最近推出了一項新功能,Tree Shakeable Providers。 Tree Shakeable Providers 是一種定義服務和其他東西的方式,以一種可以提高 Angular 應用程式性能的方式被 Angular 的依賴注入系統使用。
首先,在我們深入挖掘之前,讓我們先定義一下搖樹。搖樹是建構過程中的一個步驟,它從代碼庫中删除未使用的代碼。删除未使用的代碼可以被認為是“搖樹”,或者您可以想象一棵樹的實體搖晃和剩餘的枯葉從樹上掉下來。通過使用搖樹,我們可以確定我們的應用程式隻包含我們的應用程式運作所需的代碼。
例如,假設我們有一個實用程式庫,其中包含函數 a()、b() 和 c()。在我們的應用程式中,我們導入并使用函數 a () 和 c () 但不使用 b ()。我們希望 b() 的代碼不會被捆綁并部署給我們的使用者。搖樹是從我們發送到使用者浏覽器的已部署生産代碼中删除函數 b() 的機制。
為什麼過去版本的 Angular 中,服務已經不能被搖樹優化?這其實又回到了我們如何在 Angular 的早期版本中注冊 Service 的問題。讓我們看一個示例,說明我們如何在以前的 Angular 版本中注冊一個用于依賴注入的服務。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { SharedService } from './shared.service';
@NgModule({
imports: [BrowserModule, FormsModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
providers: [SharedService]
})
export class AppModule {}
如您所見,我們導入了服務并将其添加到我們的 Angular AppModule。 這會将服務注冊到 Angular 的依賴注入系統。 每當元件請求使用此服務時,Angular 的 DI 将確定建立 Service 及其任何依賴項并将其傳遞給元件的構造函數。
此注冊系統的問題在于,建構工具和編譯器很難确定我們的應用程式中是否使用了此代碼。
搖樹系統删除代碼的主要方式之一是檢視我們定義的導入路徑。 如果類或函數未導入,則不會包含在我們提供給使用者的生産代碼包中。如果它是導入的,則搖樹器假定它正在應用程式中使用。在我們上面的示例中,我們在 AppModule 中導入和引用我們的服務,導緻顯式依賴項不能被搖樹優化掉。
Angular Tree Shaking Providers
使用 Tree Shaking Providers (TSP),我們可以使用不同的機制來注冊我們的服務。 使用這種新的 TSP 機制将提供搖樹性能和依賴注入的好處。 我們有一個帶有特定代碼的示範應用程式來示範我們如何注冊這些服務的不同性能特征。 讓我們來看看新的 TSP 文法是什麼樣的。
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SharedService {
constructor() {}
}
在@Injectable 裝飾器中,我們有一個名為providedIn 的新屬性。有了這個屬性,我們可以告訴 Angular 将我們的服務注冊到哪個子產品,而不必導入子產品并将其注冊到 NgModule 的提供者。也就是說,不需要像舊版本的 Angular 那樣,在 AppModule 裡顯式 import 服務,并添加到 NgModule 的 providers 數組裡。
預設情況下,此文法将其注冊到根注入器,這将使我們的服務成為應用程式範圍的單例。 對于大多數用例,根提供程式是合理的預設值。 如果您仍然需要控制服務執行個體的數量,Angular 子產品群組件上的正常提供程式 API 仍然可用。
使用這個新 API,您可以看到,由于我們不必将服務導入 NgModule 進行注冊,是以我們沒有明确依賴。 因為沒有 import 語句,建構工具可以確定該服務僅在元件使用時才捆綁在我們的應用程式中。 讓我們看一個示例應用程式。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';
import { Shared3Service } from './shared3.service';
@NgModule({
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([
{ path: '', component: HelloComponent },
{
path: 'feature-1',
loadChildren: () => import('./feature-1/feature-1.module').then(m => m.Feature1Module)
},
{
path: 'feature-2',
loadChildren: () => import('./feature-2/feature-2.module').then(m => m.Feature2Module) }
}
])
],
declarations: [AppComponent, HelloComponent],
bootstrap: [AppComponent],
providers: [Shared3Service]
})
export class AppModule {}
在這個示例應用程式中,我們有三個元件; 兩個是延遲加載的子產品,而一個是我們的着陸首頁元件。 我們還将在應用程式中使用三種不同的服務。 讓我們從第一個服務開始,看看它是如何使用的。
import { Injectable } from '@angular/core';
console.log('SharedService bundled because two components use it');
@Injectable({
providedIn: 'root'
})
export class SharedService {
constructor() {
console.log('SharedService instantiated');
}
}
我們的第一個服務使用 tree shakable providers API。 我們在每個延遲加載的功能子產品中導入此服務兩次,如下所示。
import { Component, OnInit } from '@angular/core';
import { SharedService } from './../shared.service';
@Component({
selector: 'app-feature-1',
templateUrl: './feature-1.component.html',
styleUrls: ['./feature-1.component.css']
})
export class Feature1Component implements OnInit {
constructor(private sharedService: SharedService) {}
ngOnInit() {}
}
因為我們的兩個元件中都使用了服務 1,是以代碼被加載并捆綁到我們的應用程式中。 如果我們檢查控制台,我們會看到以下消息:
SharedService bundled because two components use it
第二個服務:
import { Injectable } from '@angular/core';
console.log('Shared2Service is not bundled because it not used');
@Injectable({
providedIn: 'root'
})
export class Shared2Service {
constructor() {}
}
如果我們檢查控制台,我們不會看到日志消息。 這是因為我們的功能子產品或元件中均未使用此服務。 由于它沒有被使用,是以沒有捆綁和加載代碼。
最後,我們類似于前兩個服務的第三個服務如下所示:
import { Injectable } from '@angular/core';
console.log('Shared3Service bundled even though not used');
@Injectable()
export class Shared3Service {
constructor() {}
}
console 資訊:
Shared3Service bundled even though not used
因為 Shared3Service 是使用較舊的提供程式 API 注冊的,是以由于需要注冊的 import 語句,它會建立顯式依賴項。即使沒有元件使用它,import 語句也會導緻建構系統包含并加載此代碼。
在這三個服務之間,我們可以看到搖樹系統如何在我們的應用程式中包含或删除代碼的特征。使用 TSP API,我們的服務仍然是單例的,即使對于我們示例中的延遲加載子產品中使用的服務。如果我們加載示例應用程式,我們會注意到,如果我們在功能一和功能二之間路由,則 SharedService 中的控制台日志隻會被調用一次。一旦請求子產品,Angular 将執行個體化并確定該執行個體在應用程式的剩餘生命周期中使用。
Angular Tree Shakeable Providers 為我們的應用程式提供了更好的性能,并減少了建立可注入服務所需的樣闆代碼量。