接續 上一篇(v15 到 v17),這篇將介紹 Angular v18 到 v20 中我整理的主要的新功能。


Angular v18

Zoneless change detection

  • 在之前 Angular 依賴 zone.js 來觸發變更偵測,但 zone.js 在開發體驗及效能方面有一些缺點。Angular v18 開始推出「zoneless」的實驗性 API,讓開發者可以不再依賴 zone.js。

  • 在 zoneless 模式下,官方建議在元件中使用 signals 來管理狀態,變更偵測將依據 signal 的更新來觸發。

  • 使用 zoneless 範例:

    1
    2
    3
    4
    5
    6
    7
    // main.ts

    bootstrapApplication(App, {
    providers: [
    provideExperimentalZonelessChangeDetection()
    ]
    });
    1
    2
    3
    4
    5
    // angular.json

    "polyfills": [
    "zone.js" // remove this line if you are using zoneless mode
    ],

預設啟用 Coalescing

v18 開始,新專案無論是 zoneless 或 zone.js ,都將預設啟用 coalescing,其作用為將短時間內多次的變更偵測需求合併,減少不必要的變更以提升效能。

  • 使用 zone.js 的舊專案可以新增 eventCoalescing: true 來啟用此功能。

    1
    2
    3
    4
    5
    6
    7
    // main.ts

    bootstrapApplication(App, {
    providers: [
    provideZoneChangeDetection({ eventCoalescing: true })
    ]
    });

ng-content 支援預設內容

  • 過去 ng-content 只能用來投影外部傳入的內容, v18 開始支援預設,當父元件沒有傳入內容時,就會顯示預設內容。

    1
    2
    3
    <ng-content select=".custom-content">
    <span>Here is the default content</span>
    </ng-content>

AbstractControl 新增 event 屬性

  • v18 開始為 AbstractControl 新增 event 屬性,它將回傳一個 observable,讓開發者可以更方便地監聽各種表單事件。

    1
    2
    3
    4
    const control = new FormControl<string|null>('test', Validators.required);
    control.event.subscribe(event => {
    console.log('Form control event:', event);
    });

Route redirectTo 支援函式

  • 在 Angular v18,redirectTo 現在可以接受一個函式,讓你根據執行時狀態動態決定要導向哪個路徑。這樣可以實現更複雜的導向邏輯,例如根據登入狀態、權限等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const routes: Routes = [
    {
    path: '',
    redirectTo: ({ queryParams }) => {
    const userId = queryParams['userId'];
    const isLoggedIn = !!userId && checkUserLoggedIn(userId);
    return isLoggedIn ? '/dashboard' : '/login';
    }
    }
    ];

Event replay

  • 在使用 SSR 且啟用 hydration 時,當頁面尚未完成「hydration」時,使用者的操作(例如點擊、輸入等)將不會有作用。

  • v18 開始支援預覽版的 Event replay (事件重播),Angular 會先記錄所有使用者事件,等到頁面完成 hydration 後再重播這些事件,確保不會漏掉這些互動。

  • 使用 withEventReplay() 來啟用,範例:

    1
    2
    3
    4
    5
    6
    7
    // main.ts

    bootstrapApplication(App, {
    providers: [
    provideClientHydration(withEventReplay())
    ]
    });

v18 其他更新

  • @if 等新的控制流程語法進入穩定版
  • @defer 等延遲載入語法進入穩定版
  • Material 3 進入穩定版
  • HttpClientModule 棄用
    • 改用 provideHttpClient() 來取代。

      1
      2
      3
      bootstrapApplication(App, {
      providers: [provideHttpClient()]
      });

Angular v19

Incremental Hydration

  • v19 新增 Incremental Hydration(增量式水合)的開發者預覽版,讓頁面可以分段進行 hydration,而不是一次性將整個頁面水合完成。這樣可以提升初始載入效能,讓使用者更快看到可互動的內容。例如,頁面一開始只有主選單活化,其他部分灰階(未水合),等使用者滑到或點擊才載入對應元件將其「水合」。

  • 使用 withIncrementalHydration() 來啟用,範例:

    1
    2
    3
    4
    5
    // main.ts

    bootstrapApplication(AppComponent, {
    providers: [provideClientHydration(withIncrementalHydration())]
    });
    1
    2
    3
    4
    5
    6
    <!-- html -->

    <!-- 當使用者點擊 <promo-banner> 時才水化元件 -->
    @defer (hydrate on interaction(click)) {
    <promo-banner/>
    }

ServerRoute 和 RenderMode

  • 以往啟用 SSR(Server-Side Rendering)時,Angular 會有以下方式處理路由:

    • 針對沒有參數的路由進行 prerender,在建置階段,預先產生好 HTML 靜態檔案部署到伺服器上,使用者請求時直接回傳這些靜態檔案。
    • 針對有參數的路由進行 server-side rendering,伺服器在收到請求時,根據路由參數動態產生 HTML 回傳給使用者。
  • 在 v19 中,可以用新的 ServerRoute 介面,針對每條路由指定渲染模式,包含:

    • Prerender(預渲染)
    • Server(伺服器端渲染)
    • Client(客戶端渲染)
    1
    2
    3
    4
    5
    const serverRouteConfig: ServerRoute[] = [
    { path: '/login', mode: RenderMode.Server }, // 伺服器端渲染
    { path: '/dashboard', mode: RenderMode.Client }, // 客戶端渲染
    { path: '/**', mode: RenderMode.Prerender }, // 其他路由預渲染
    ];
  • 這主要解決了帶參數路由的預渲染需求,不需重複宣告路由;
    也能針對高度動態、不需 SEO 的內容(儀表板、登入後的會員頁面等),改用客戶端渲染以減少伺服器負擔。

Hot Module Replacement (HMR)

  • 過去使用 Angular 開發時,每次修改元件樣式或模板並儲存檔案後,Angular CLI 都會重建整個應用程式並刷新瀏覽器,且導致應用狀態(例如表單輸入、滾動位置)會重置。

  • v19 新增的 HMR 能直接編譯你修改的樣式或模板,只把變更部分即時套用到瀏覽器,無需整頁刷新,也不會丟失狀態。這可以大幅提升開發效率及體驗。

    • 樣式(style) 的 HMR 預設啟用。

    • 模板(template) 的 HMR 在 v19 中屬於實驗功能,需要用環境變數啟用:

      1
      NG_HMR_TEMPLATES=1 ng serve
    • 關閉 HMR 可用 --no-hmr 參數:

      1
      ng serve --no-hmr

v19 其他更新

  • Standalone 預設啟用;可以使用以下設定來強制使用 standalone 元件:

    1
    2
    3
    "angularCompilerOptions": {
    "strictStandalone": true
    }
  • Angular CLI 會對 standalone 中未使用的 imports 發出警告(unusedStandaloneImports)。

  • @let 語法進入穩定版,它允許在 template 裡能直接宣告區域變數。

    1
    2
    <input #name>
    @let greeting = 'Hello ' + name.value;
  • 新專案的 Event replay 將預設啟用


Angular v20

新的實驗性 resource 相關 API

Angular v19 開始推出 resource API,v20 增加 resource streaming 和 httpResource,
目的是用 Signal-based reactive API 更方便管理非同步狀態。

resource API

  • 讓你在 signal 改變時,啟動非同步行為(如請求資料),並將結果以 signal 形式暴露。

  • 主要用法:

    1
    2
    3
    4
    5
    6
    7
    const userId: Signal<string> = getUserId();
    const userResource = resource({
    params: () => ({id: userId()}),
    loader: ({request, abortSignal}): Promise<User> => {
    return fetch(`users/${request.id}`, {signal: abortSignal});
    },
    });
    • userId signal 改變時,會自動重新發起請求。

Streaming Resource

  • 用於資料流(例如 WebSocket),可持續接收數據。

  • 範例用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Component({
    template: `{{ dataStream.value() }}`
    })
    export class App {
    dataStream = resource({
    stream: () => {
    return new Promise<Signal<ResourceStreamItem<string[]>>>(resolve => {
    const resourceResult = signal<{ value: string[] }>({ value: [] });
    this.socket.onmessage = event => {
    resourceResult.update(current => ({
    value: [...current.value, event.data]
    }));
    };
    resolve(resourceResult);
    });
    },
    });
    }
    • resourceResult 透過 signal 持續更新 WebSocket 收到的資料。

httpResource

  • 以更簡潔的 signal API 進行 HTTP 請求。

  • 範例用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Component({
    template: `{{ userResource.value() | json }}`
    })
    class UserProfile {
    userId = signal(1);
    userResource = httpResource<User>(() =>
    `https://example.com/v1/users/${this.userId()}`
    );
    }
    • userResource 會隨著 userId signal 變化,重新發送 GET 請求。
    • 提供如 isLoadingheaders 等屬性。
  • 可透過 HttpClient 設定攔截器:

    1
    2
    3
    4
    5
    bootstrapApplication(AppComponent, {providers: [
    provideHttpClient(
    withInterceptors([loggingInterceptor, cachingInterceptor]),
    )
    ]});

動態元件創建與綁定

  • 之前可使用 createComponent 動態建立元件,v20 新增可直接套用指令與指定綁定功能。

  • 支援 signal-based 綁定、雙向綁定。

  • 範例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import {createComponent, signal, inputBinding, outputBinding} from '@angular/core';

    const canClose = signal(false);
    const title = signal('My dialog title');

    createComponent(MyDialog, {
    bindings: [
    inputBinding('canClose', canClose), // input 綁定 signal
    outputBinding<Result>('onClose', result => console.log(result)), // output 綁定 callback
    twoWayBinding('title', title), // 雙向綁定
    ],
    directives: [
    FocusTrap, // 套用指令
    {
    type: HasColor,
    bindings: [inputBinding('color', () => 'red')] // 指令綁定 input
    }
    ]
    });
    • 可將 signal 綁定到 input、output 綁定 callback、雙向綁定 signal。
    • 可直接套用 directives 並指定 input 綁定。

檔名和元件後綴變成「選擇性」

  • Angular CLI 從 v20 起,預設不會為元件、指令、服務等產生後綴。

  • 舊專案透過 ng update 會在 angular.json 啟用後綴產生。

  • 鼓勵開發者更有意識地命名抽象,減少樣板程式碼。

  • 若新專案需要產生後綴,可於 angular.json 設定 schematic:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    "projects": {
    "app": {
    "schematics": {
    "@schematics/angular:component": { "type": "component" },
    "@schematics/angular:directive": { "type": "directive" },
    "@schematics/angular:service": { "type": "service" },
    "@schematics/angular:guard": { "typeSeparator": "." },
    "@schematics/angular:interceptor": { "typeSeparator": "." },
    "@schematics/angular:module": { "typeSeparator": "." },
    "@schematics/angular:pipe": { "typeSeparator": "." },
    "@schematics/angular:resolver": { "typeSeparator": "." }
    },
    }
    }
    }

v20 其他更新

  • SSR 的 incremental hydrationroute-level rendering mode 進入穩定版

  • 擴充模板表達式語法: **in 運算子

    1
    2
    3
    4
    5
    <!-- n 的平方 -->
    {{ n ** 2 }}

    <!-- 判斷 person 是否有 name 屬性 -->
    {{ name in person }}
  • 為應對 Karma 的棄用,Angular CLI 實驗性的引入了 vitest 測試。


小結

整體來說,Angular 近幾年迎來了翻天覆地的變化,像是 zone.js 原本是舊專案變化檢測的核心,但現在卻被 zoneless 的方式逐漸取代,並且推薦使用 Standalone 和 Signals 來管理狀態。對舊專案進行升級難免會遇到一些轉換成本,但這些改動也確實提升了效能與開發體驗。而且官方也有提供 升級指南,因此如果手上還有舊專案,還是建議可以在有餘力時升級到最新穩定版本喔。


參考來源