📝

Angular Signalの使用指南

に公開

1. Signal の概念

Signal は変化可能な値であり、値が変化したときに自動的にすべての使用箇所に通知します。Signal はプリミティブ型から複雑なデータ構造まで、あらゆる値を保持できます。

2. 基本的な使用方法

Signal の作成

import { Component, signal } from '@angular/core';
@Component({
    selector: 'app-lesson3',
    imports: [],
    templateUrl: './lesson3.component.html',
    styleUrl: './lesson3.component.less'
})
export class Lesson3Component {
    // Signalの作成
    count = signal(0);
    name = signal('山田一二');
    isActive = signal(true);
    obj = signal({
        name: '山田さん',
        age: 20
    });
}

Signal 値の読み取り

HTMLテンプレートでのSignal読み取り構文は以下の通り:

<p>数字:{{count()}}</p>
<p>文字列:{{name()}}</p>
<p>真偽値:{{isActive()}}</p>
<p>オブジェクト:<br>
    <span>{{obj().name}}</span><br>
    <span>{{obj().age}}</span>
</p>

見た目は通常の変数読み取りと変わりませんが、通常の変数はページ上で動的に更新できません。これがSignalと通常の変数の最大の違いです。

Signal の更新

Signalの値を更新するには、直接.set()するか、または.update()を使用して前の値から新しい値を計算します:

// signal の値を変更する
changeSignals() {
    // 1. 値を直接変更する
    this.count.set(2);
    this.isActive.set(false);
    // 2. 元の値を基に更新する
    this.name.update(value => value + '三');
    this.obj.update(value => ({
        ...value,
        age: value.age + 1
    }));
}
<p><button (click)="changeSignals()">Signalsを変更</button></p>
<p>数字:{{count()}}</p>
<p>文字列:{{name()}}</p>
<p>真偽値:{{isActive()}}</p>
<p>オブジェクト:<br>
    <span>{{obj().name}}</span><br>
    <span>{{obj().age}}</span>
</p>

3. 計算値 (computed)

計算 Signal は読み取り専用のSignalであり、その値は他のSignalから派生します。computed関数を使用して派生方法を指定することで、計算Signalを定義できます。

計算値には以下の特徴があります:

  1. 他のSignalに依存し、読み取り専用で直接代入できない
  2. 初回読み取り時には計算が発生しない
  3. 計算された値はキャッシュされ、値が変化したときのみ再計算される
// 計算
total = computed(() => {
    return this.obj().name + ',' + this.obj().age;
});
<p>計算:{{total()}}</p>

4. 変更の監視 (effect)

Signalは、値が変更されたときに興味のある利用者に通知するため、非常に有用です。

実際の業務シナリオでは、1つまたは複数のSignal値が変更されたときに他の操作(データの再取得など)をトリガーする必要がよくあります。このような場合、effect関数を使用して副作用を作成し、すべてのSignalの変更を監視できます。

副作用は、通常constructorコンストラクタ内に記述します。

constructor() {
    // 副作用
    effect(() => {
        console.log('名前が変更されました:', this.name());
        console.log('カウントが変更されました:', this.count());
    });
}

副作用は常に変更検知プロセス中に非同期的に実行されます。

副作用を作成すると、その囲んでいるコンテキストが破棄されるときに自動的に破棄されます。これは、コンポーネント内で作成された副作用はコンポーネントが破棄されるときに破棄されることを意味します。ディレクティブ、サービスなどにおける副作用も同様です。

副作用はEffectRef型のオブジェクトを返し、.destroy()メソッドを呼び出すことで手動で破棄できます。

effectRef: EffectRef = null as any;

constructor() {
    // 副作用
    this.effectRef = effect(() => {
        console.log('名前が変更されました:', this.name());
        console.log('カウントが変更されました:', this.count());
    });
}
<p><button (click)="changeSignals()">Signalsを変更</button></p>
<p><button (click)="effectRef.destroy()">副作用をクリア</button></p>

注意:副作用を破棄すると、変更監視の副作用は機能しなくなります。

5. 完全な簡単な例

import { Component, computed, effect, EffectRef, untracked } from '@angular/core';
import { signal } from '@angular/core';

@Component({
    selector: 'app-lesson3',
    imports: [],
    templateUrl: './lesson3.component.html',
    styleUrl: './lesson3.component.less'
})
export class Lesson3Component {
    // Signalの作成
    count = signal(0);
    name = signal('山田一二');
    isActive = signal(true);
    obj = signal({
        name: '山田さん',
        age: 20
    });

    effectRef: EffectRef = null as any;

    constructor() {
        // 副作用
        this.effectRef = effect(() => {
            console.log('名前が変更されました:', this.name());
            console.log('カウントが変更されました:', this.count());
            const currentB = untracked(() => this.count());
        });
    }

    // signal の値を変更する
    changeSignals() {
        // 1. 値を直接変更する
        this.count.set(2);
        this.isActive.set(false);
        // 2. 元の値を基に更新する
        this.name.update(value => value + '三');
        this.obj.update(value => ({
            ...value,
            age: value.age + 1
        }));
    }

    // 計算
    total = computed(() => {
        return this.obj().name + ',' + this.obj().age;
    });
}
<p><button (click)="changeSignals()">Signalsを変更</button></p>
<p><button (click)="effectRef.destroy()">副作用をクリア</button></p>
<p>数字:{{count()}}</p>
<p>文字列:{{name()}}</p>
<p>真偽値:{{isActive()}}</p>
<p>オブジェクト:<br>
    <span>{{obj().name}}</span><br>
    <span>{{obj().age}}</span>
</p>

<p>計算:{{total()}}</p>

6. よく使われる API まとめ

  • signal(初期値) - Signalを作成
  • signal() - 値を読み取り
  • signal.set(新しい値) - 新しい値を設定
  • signal.update(関数) - 現在の値に基づいて更新
  • computed(関数) - 計算値を作成
  • effect(関数) - 変更を監視

Discussion