最佳實務
效能

延遲載入服務

IMPORTANT: 若要讓延遲載入運作,你載入的服務必須是自動提供的。請用 @Injectable({providedIn: 'root'}) 或 @Service() 裝飾它。若沒有自動提供,Angular 在服務載入後就無法建構它。

Angular 的 injectAsync 函式讓你按需載入服務,只在真正需要時才載入。當服務依賴大型函式庫或不常使用的功能、而你不想在初始頁面載入時就付出這個成本時,這非常有用。

當你使用 injectAsync 時,服務的程式碼會被你的打包工具拆成獨立的 JavaScript chunk,在你第一次索取實例時下載。載入後,Angular 會透過一般的 DI 系統解析該服務,因此它仍然可以依賴其他可注入項,行為也和任何其他單例一樣。

延遲注入服務

想像一個依賴厚重試算表函式庫的 ReportExporter。大多數使用者只會開啟報表,只有少數人會點擊 Export。按需載入匯出器:

import {Component, injectAsync} from '@angular/core';

@Component({
  selector: 'app-report',
  template: `<button (click)="export()">Export</button>`,
})
export class Report {
  private exporter = injectAsync(() => import('./report-exporter').then((m) => m.ReportExporter));

  async export() {
    const exporter = await this.exporter();
    exporter.export();
  }
}

第一次呼叫 this.exporter() 會觸發動態匯入,並從 DI 解析該服務。後續呼叫會重複使用同一個 promise,因此 chunk 只會擷取一次。

如果延遲載入的服務是預設匯出,可以直接傳入動態匯入,Angular 會幫你解開 default:

report-exporter.ts

@Service()
export default class ReportExporter {
  /* … */
}

report.ts

private exporter = injectAsync(() => import('./report-exporter'));

預先擷取依賴

依預設,延遲的 chunk 只在你叫用傳回的函式時才會擷取。你可以在選項中傳入 prefetch 觸發器,讓下載更早開始。觸發器是任何傳回 Promise 的函式,當它解析完成時,Angular 就會啟動載入器。

Angular 內建了 onIdle 觸發器,它會等到瀏覽器閒置時才觸發:

import {Component, injectAsync, onIdle} from '@angular/core';

@Component({/* … */})
export class Report {
  private exporter = injectAsync(() => import('./report-exporter').then((m) => m.ReportExporter), {
    prefetch: onIdle,
  });
}

你也可以為 onIdle 設定最長等待時間,讓預先擷取在已知的時間範圍內一定會發生,即使頁面很忙也一樣:

injectAsync(loader, {prefetch: () => onIdle({timeout: 1_000})});

NOTE: 預先擷取是機會性的。如果使用者在預先擷取觸發前就叫用該功能,Angular 仍會立即載入依賴,並在準備好時儘快解析你的 await。

提供自訂預先擷取觸發器

PrefetchTrigger 就只是一個傳回 promise 的函式,promise 一解析完成,載入器就會執行。用它來把預先擷取與你自己的訊號對齊,例如懸停事件或排程器的 tick:

import {PrefetchTrigger} from '@angular/core';

export function onHover(target: HTMLElement): PrefetchTrigger {
  return () =>
    new Promise<void>((resolve) => {
      target.addEventListener('pointerenter', () => resolve(), {once: true});
    });
}