🔥
Angular SignalsのEffectは即時関数と使うとわかりやすい
Angular SignalsのEffectは、Effect内で利用しているSignalsが変化してる度に実行される便利な機能です。
effect(() => {
console.log('signal variable is changed to ' + signalVariable());
})
しかし、こうしてSignalが最初に呼び出されている時は問題なく実行されるのですが、非同期や条件文の中では呼び出しがスキップされます。
effect(() => {
// this.countが変化する度に実行される
this.insideSimpleEffect = this.count();
});
effect(async () => {
// 実行されない
await new Promise<void>((resolve) => resolve());
this.insideAwaitRequestAnimationFrame = this.count();
});
effect(() => {
// 実行されない
requestAnimationFrame(() => {
this.insideRequestAnimationFrame = this.count();
});
});
effect(() => {
// 実行されない
setTimeout(() => {
this.insideSetTimeout = this.count();
}, 100);
});
effect(() => {
// 実行されない
if (this.insideSimpleEffect === 5) {
this.insideCondition = this.count();
}
});
https://stackblitz.com/edit/stackblitz-starters-unvngn?file=src%2Fmain.ts で実際に呼び出されないことを確認できます。
原因
Signalが実際に読み込まれた時だけ、Effectはその値に対する依存関係を登録するためです。そのため、条件文に隠されたり、非同期処理のように異なるスタックフレームで作業が行われると、エフェクトは依存関係を登録できないためにSignalの値が変わっても変更は検知されません。
Source: https://github.com/angular/angular/issues/56773#issuecomment-2198479276
対応
1. 必ず最初の行で代入を行う
依存関係を登録するために、必ず利用するSignalを最初の行で代入を行います。一番シンプル。 ただ書く人の意識次第なので、見逃しやすいです。
ts
effect(() => {
+ const count = this.count();
if (this.insideSimpleEffect === 5) {
- this.insideCondition = this.count();
+ this.insideCondition = count;
}
});
2. 即時関数を使う
Effectでは即時関数を呼び出す形にして、引数にSignalを渡すことで、依存関係を登録することができます。個人的にはこちらの方が好きで、何のためのEffectかが明確になります。
effect(() => ((count: number) => {
if (this.insideSimpleEffect === 5) {
this.insideCondition = count;
}
})(this.count()));
まとめ
Signalsはとても使い勝手がよく、特にFormの変更検知ではとても便利です。ただ、この仕組みをわかっていないと不具合の温床になるので、ルール化して使っていくことがおすすめです。
それではまた。
Discussion