サンプルコードで身につける: 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が画面を自動更新できる理由そのものです。実際に実行して、変更通知のタイミングを確かめてみてください。
4set と update の違いを確かめる
set は「新しい値で丸ごと置き換える」、update は「現在値から次の値を計算する」という役割の違いを、簡易シグナルの実装で確かめる例です。実行すると、同じ「値を変える」操作でも2つのAPIが分かれている理由が見えてきます。カウントアップのような更新は update が安全です。
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()); // 確定した値だけを反映する
}
}