Dev Study
← 解説「signal — リアクティブな状態の入れ物」に戻る

サンプルコードで身につける: signal — リアクティブな状態の入れ物

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1signal を読んで set で置き換える

シグナルの最小の使い方で、signal() で作り、テンプレートでは userName() と関数として読み、set() で置き換えます。値が変わると表示が自動で追従する、というシグナルの基本サイクルが1画面に収まっています。普通のプロパティとの書き方の違いをまず体に入れる例です。

import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-login-greeting',
  // シグナルは userName() と関数のように呼んで読む
  template:
    '<p>こんにちは、{{ userName() }}さん</p>' +
    '<button (click)="login()">ログイン</button>',
})
export class LoginGreetingComponent {
  userName = signal('ゲスト');

  login() {
    this.userName.set('田中'); // set で置き換えると表示も変わる
  }
}

2update で現在値から次の値を作る

「今の値に+1する」のように現在値を使って更新するときは、set ではなく update を使います。数量ピッカーの増減ボタンという身近な題材で、update に渡す関数が(現在値)=>(次の値)の形だと分かります。下限チェックのようなルールも更新関数の中に自然に書けます。

import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-quantity-picker',
  template:
    '<button (click)="decrease()">-</button>' +
    ' {{ quantity() }} 個 ' +
    '<button (click)="increase()">+</button>',
})
export class QuantityPickerComponent {
  quantity = signal(1);

  increase() {
    this.quantity.update((v) => v + 1);
  }

  decrease() {
    this.quantity.update((v) => Math.max(1, v - 1)); // 1個未満にしない
  }
}

3シグナルの中身を自作して動かす

「読み書きを関数経由にして、変更を購読者に知らせる」というシグナルの核を、プレーンなTypeScriptで体験する例です。write した瞬間に subscribe したコールバックが動く様子は、Angularが画面を自動更新できる理由そのものです。実際に実行して、変更通知のタイミングを確かめてみてください。

TypeScript

4set と update の違いを確かめる

set は「新しい値で丸ごと置き換える」、update は「現在値から次の値を計算する」という役割の違いを、簡易シグナルの実装で確かめる例です。実行すると、同じ「値を変える」操作でも2つのAPIが分かれている理由が見えてきます。カウントアップのような更新は update が安全です。

TypeScript

5検索フォームの状態をシグナルで持つ

入力中の下書きと確定済みキーワードという2つの状態を、それぞれシグナルで持つ実務寄りの例です。画面の状態をシグナルに寄せておくと、Angularが変更を正確に検知して必要な部分だけを更新してくれます。実際の検索画面では、確定したシグナルの値を使ってAPIを呼ぶ流れになります。

import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-keyword-search',
  template:
    '<input (input)="onInput($event)" placeholder="キーワード">' +
    '<button (click)="search()">検索</button>' +
    '<p>検索中のキーワード: {{ submitted() }}</p>',
})
export class KeywordSearchComponent {
  draft = signal('');
  submitted = signal('');

  onInput(e: Event) {
    this.draft.set((e.target as HTMLInputElement).value);
  }

  search() {
    this.submitted.set(this.draft()); // 確定した値だけを反映する
  }
}