Angular 20 重點更新

最低版本限制

Angular Node.js TypeScript
20.0.x ^20.19.0 || ^22.12.0 || ^24.0.0 >=5.8.0 <5.9.0

使用pnpm outdated可查看可更新套件

舊專案升版

使用ng-update,但無法一次跨兩版,需分階進行v18->v19->v20

官方更新指南

Signal API 趨於穩定

signal、computed、input、effect、linkedSignal、toSignal 皆已進入穩定版本。原先使用裝飾器的語法建議改為新的signal語法

原有方法 signal方法
@Input input
@Output output
@Input/@Output model
@ViewChild viewChild
@ViewChildren viewChildren

基礎Signal

  • signal
  • 替換&更新
    • set - 直接改值,單純覆蓋
    • update - 當前值作為參數,可基於舊值更新
  • computed - 計算屬性,派生signal,僅可讀
  • effect - 副作用,響應式執行,只能存在inject context

stackblitz 範例-基礎Signal

LinkedSignal

  shippingOptions = signal<ShippingMethod[]>([
    { id: 0, name: 'Ground' },
    { id: 1, name: 'Air' },
    { id: 2, name: 'Sea' },
  ]);

  selectedOption = linkedSignal<ShippingMethod[], ShippingMethod>({
    // `selectedOption` is set to the `computation` result whenever this `source` changes.
    source: this.shippingOptions,
    computation: (newOptions, previous) => {
      // If the newOptions contain the previously selected option, preserve that selection.
      // Otherwise, default to the first option.
      return (
        newOptions.find((opt) => opt.id === previous?.value.id) ?? newOptions[0]
      );
    },
  });

stackblitz 範例 - linkedSignal

linkedSignal 與 computed 的差別

兩者皆由其他 signal 派生而來,但linkedSignal 不同的點在於他是writable signal, 可以直接對他進行值的變更,且允許存取先前的值。

image
image

httpResource API - 基於 signal 的請求-1

此部分仍處於實驗性 api

v19 導入了 resource api,Resource 提供了一種將非同步資料合併到應用程式基於訊號的程式碼中的方法

v20推出httpResource() API,提供了一種宣告式的、基於 Signal 的方式來處理 HTTP GET 請求。
能自動管理載入中 (loading)、錯誤 (error) 和資料 (data) 狀態,大幅減少了樣板程式碼。httpResource() 在底層使用 HttpClient。

httpResource API - 基於 signal 的請求-2

@Component({
  selector: 'app-user-profile',
  template: `
    @switch (userResource.state()) {
      @case ('loading') { <p>Loading...</p> }
      @case ('error') { <p>Error fetching user data</p> }
      @case ('resolved') { 
        <pre>{{ userResource.value() | json }}</pre> 
      }
    }`
})
export class UserProfile {
  userId = signal(1);
  userResource = httpResource<User>(() => 
    `https://example.com/v1/users/${this.userId()}`
  );
}

resource & httpResource showcase

如果一個請求已經處於待處理狀態,會自動先取消未完成的請求,然後再發出新的請求

stackblitz 範例-resource & httpResource

zoneless

zoneless已經是預覽版本

  • 新專案 - 使用 cli 建立專案時可以選擇採用zoneless
  • 舊專案調整如下
export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideZonelessChangeDetection(),
  ]
};

從 angular.json 移除 zone.js polyfill, 解安裝npm uninstall zone.js

zoneless 後變更偵測的時機

觸發時機 說明
在模板中綁定 DOM 事件(例如 (click)) 當使用者與畫面互動時(如點擊按鈕)會觸發變更偵測
Signal 被更新 當使用 signal 的值被改變時會觸發變更偵測
模板中使用了 async 管線符(async pipe) 當 async pipe 訂閱的 Observable/Signal 發生變化時
使用 ComponentRef.setInput() 改變輸入參數 手動設定元件輸入屬性時會觸發變更偵測
手動呼叫 ChangeDetectorRef.markForCheck() 明確標記元件需要檢查變更
元件被建立或銷毀 新增或移除元件時,自動觸發變更偵測

動態建立元件 createComponent - 1

以往動態建立元件需要更改input主要有兩種方法

1.使用instance來變更 > 缺點:手動設定的input屬性不會觸發onChange生命週期

#vcr = inject(ViewContainer);
ngOnInit(){
    const ref = this.#vcr.createComponent(MyComponent);
    ref.instance.text = 'myText'
}

2.使用setInput()

#vcr = inject(ViewContainer);
ngOnInit(){
    const ref = this.#vcr.createComponent(MyComponent);
    ref.setInput('text', 'myText')
}

動態建立元件 createComponent - 2

新增inputBinding、outputBinding以及twoWayBinding,讓屬性綁定和指令變得更加容易

export class AppComponent {
 readonly vcr = viewChild.required('container', { read: ViewContainerRef });
 readonly canClose = signal(true)
 readonly isExpanded = signal(true)

 createWarningComponent(): void {
   this.vcr().createComponent(AppWarningComponent, {
       bindings: [
           inputBinding('canClose', this.canClose),
           twoWayBinding('isExpanded', this.isExpanded),
           outputBinding<boolean>('close', (isConfirmed) => console.log(isConfirmed))
       ],
       directives: [
           FocusTrap,
           {
               type: ThemeDirective,
               bindings: [inputBinding('theme', () => 'warning')]
           }
       ]
   })
 }
}

模板語法 Tagged Template Literals

帶標記模板,可以把文字跟變數分開提取出來進行轉換

Pipes 跟 Tagged template literals的差別: 雖然這兩者都是做到將值轉換,但Tagged template literals 可以直接存取元件的屬性跟方法

@Component({
 template: `<p>{{greet`hello, ${name()}`,
})

export class AppComponent {
 name = signal('Jack');
 greet(strings: TemplateStringsArray, name: string){
   return `${strings[0]}${name}`;
  }
}
模板指數運算 **

現在可以於模板中使用指數運算符
{{ 2**2 }} =>8

支援 in 運算子

用來辨別物件是否含有指定屬性,只有判別是否有相符屬性,即便屬性是undefined或是null皆為true

@Component({
 template: `@if('age' in person){ 有年齡資料 }`,
})

export class AppComponent {

 person = {
     name: 'Jack',
     age: 18
 }
}

開發者工具更新

新增 show signal graph beta 可以圖形化顯示 signal 相依關係。

目前仍處於beta,預設關閉,需於設定面板中打開

Control flow

過去的*ngIf 、 *ngFor 和 *ngSwitch 預計v22會被移除,建議都使用新的@if 、 @for等方法。

一指令轉換:

ng generate @angular/core:control-flow

Style Guide

預設已經不會為透過 cli gen 的元件服務等產生後綴,若要啟動加入後綴要於 angular.json 新增額外設定

{
  "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": "." }
      },
  ...
}

AI輔助開發 - Angular cli Mcp server

輸入指令 ng mcp 即會產生mcp設定檔

vscode設定方式

於專案下的 .vscode/mcp.json 貼上以下內容

{
  "servers": {
    "angular-cli": {
      "command": "npx",
      "args": ["@angular/cli", "mcp"]
    }
  }
}

AI輔助開發 - Angular Best Practices Guide

官方提供之 Angular 最佳實踐的系統指令,以符合新語法與風格指南

官方提供之範本

給編輯器的規則文件

reference: https://angular.dev/ai/develop-with-ai

image

Angular 20 相關參考資料

Thanks!