2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Angular】Computed Signal が更新されない!

Last updated at Posted at 2024-12-18

はじめに

v16 から Angular に Signal が導入されました。
シンプルな記述でリアクティブな値を管理でき、非常に便利です。
また、Signal を使った変更検知は、従来の zone.js によるものと異なり、コンポーネントツリー全体を確認する必要がないため、パフォーマンスの向上も期待できます。
しかし、使い勝手が良い反面、注意が必要な現象に遭遇し沼ったため、備忘録として残します。

Computed Signal とは

Computed Signal とは、Signal から派生した値を持つ読み取り専用の Signal です。
以下のように、computed関数を使って定義します。

const count: WritableSignal<number> = signal(0);
const doubleCount: Signal<number> = computed(() => count() * 2);

Computed Signal が更新されないケース

Computed Signal を定義時点で、computed関数に渡す Signal を評価できないと、更新を追跡できないようです。
配列やオブジェクトのイテレーションメソッドに渡すコールバック関数で Signal を評価する場合、そういったことが起こる可能性があります。

以下のように、初期値がundefinedの配列undefinedNumbersのイテレーションメソッド内では、定義時点で this.count() を評価できません。
結果、後からundefinedNumbersに値を代入しても、countの更新を追跡できず、undefinedComputedSignalundefinedのままになってしまうようです。

main.ts
import { Component, computed, signal } from "@angular/core";
import { bootstrapApplication } from "@angular/platform-browser";

@Component({
  selector: "app-root",
  template: `
    <div>undefined computed signal: {{ undefinedComputedSignal() }}</div>
    <button (click)="countUp()">count up</button>
  `,
})
export class App {
  count = signal(0);
  undefinedNumbers: number[];
  undefinedComputedSignal = computed(() =>
    this.undefinedNumbers?.find((number) => number === this.count())
  );

  countUp() {
    this.count.update((count) => count + 1);
    this.undefinedNumbers = [5, 10, 15];
  }
}

bootstrapApplication(App);

回避策

以下のように、イテレーションメソッドのコールバック関数外で参照を作ってあげると、countの更新を追跡してくれます。

-  undefinedComputedSignal = computed(() =>
-    this.undefinedNumbers?.find((number) => number === this.count())
-  );
+  undefinedComputedSignal2 = computed(() => {
+    const count = this.count();
+    return this.undefinedNumbers?.find((number) => number === count);
+  });
main.ts
import { Component, computed, signal } from "@angular/core";
import { bootstrapApplication } from "@angular/platform-browser";

@Component({
  selector: "app-root",
  template: `
    <div>undefined computed signal2: {{ undefinedComputedSignal2() }}</div>
    <button (click)="countUp()">count up</button>
  `,
})
export class App {
  count = signal(0);
  undefinedNumbers: number[];
  undefinedComputedSignal2 = computed(() => {
    const count = this.count();
    return this.undefinedNumbers?.find((number) => number === count);
  });

  countUp() {
    this.count.update((count) => count + 1);
    this.undefinedNumbers = [5, 10, 15];
  }
}

bootstrapApplication(App);

StackBlitz

検証のために遊んだ StackBlitz を貼っておきます。
https://stackblitz.com/edit/stackblitz-starters-57skebhe?file=src%2Fmain.ts

おわりに

Computed Signal は定義時に評価した Signal の更新を追跡するようです。
紹介したケースのように、undefined, [], {}のイテレーションメソッド内に追跡したい Signal を書くと、この不具合が発生する可能性があるため、注意が必要です。

最後に、本記事の内容は、あくまで実験の結果であって、Signalの挙動を保証するものではない点にはご注意ください。

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?