Angular 16 中最重要的API就是 Signal API。一個Signal可以持有原始值或物件,當值更新時會通知範本和相依的 Signals。
不同於有數百個RxJS operators,Signal API非常簡單,只有少數幾個方法。
count = signal(5); // a number Signal
hello = signal('Hello'); // a string Signal
結果是一個型態為 WritableSignal 的實例。
API 中有兩個方法可以修改既有的Signal:set()
和 update()
。set
方法會覆寫 Signal 的目前值,而update
方法會執行一個回呼函數 (callback),根據目前值來產生新的值。
this.count.set(10); // 將 count Signal 設定為 10
this.hello.update((prev) => `${prev} World.`); // 將 hello Signal 更新為 Hello World
WritableSignal
有個asReadonly
方法可以建立一個唯讀的 Signal。唯讀的Signal沒有set
和 update
方法,呼叫這些方法時編譯器會發出錯誤訊息。
countRead = this.count.asReadonly(); // 建立一個唯讀的 Signal
countRead.set(1000); // 會產生錯誤
computed
函數可以從既有的Signal衍生出一個唯讀的 Signal。同樣地,它也沒有修改值的方法。當它相依的任何`Signal更新時,回呼函數會重新執行以計算新的值。
double = computed(() => count() * 2);
triple = computed(() => count() * 3);
例如,當count
Signal從5更新為10時,double
和triple
的新值分別是20
和30
。
在範本中顯示Signal值
<div>
<p>name: {{ name() }}</p>
<p>count: {{ count() }}</p>
</div>
要在HTML範本中顯示Signal值,可以將它們作為函數呼叫插入double curly braces。當name
或count
signals更新時,組件會被標記為髒,下一次變更偵測時,範本會重新渲染以顯示新值。
Effects
在應用程式需要關注Signal
變更時很有用。Effect
必須在有效的injection context中執行,例如constructor或欄位初始化 (Field initializers)。
// 在建構函數中
constructor() {
effect(() => console.log('Effect in constructor', this.count(), this.hello()));
}
// 在欄位初始化中
#logEffect = effect(() => console.log('Effect in field initialization', this.count(), this.hello()));
範例中的console.log
會在count
或hello
變更時執行。
import { Component, computed, effect, signal } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<h1>Hello from {{ name }}!</h1>
<p>Count: {{ count() }}</p>
<p>Hello: {{ hello() }}</p>
<p>Double: {{ double() }}</p>
<p>Triple: {{ triple() }}</p>
<button (click)="update()">Click me to update signal</button>
<button (click)="count.set(5); hello.set('Hello')">Reset</button>
`,
})
export class App {
name = 'IT Home Ironman 2024 day 3';
count = signal(5);
hello = signal('Hello');
countRead = this.count.asReadonly();
double = computed(() => this.count() * 2);
triple = computed(() => this.count() * 3);
// in field initialization
#logEffect = effect(() => console.log('Effect in field initialization', this.count(), this.hello()));
constructor() {
effect(() => {
console.log('Effect in constructor', this.count(), this.hello());
})
}
update() {
this.count.set(10);
this.hello.update((prev) => `${prev} World.`);
// compiler error
// this.countRead.set(200);
// compiler error
// this.double.set(200);
}
}
這是我2024鐵人賽第3天的心得分享。