Angularシグナル
Angularシグナルは、アプリケーション全体で状態がどのように使用されているかを細かく追跡するシステムであり、フレームワークがレンダリングの更新を最適化することを可能にします。
TIP: この包括的なガイドを読む前に、Angularの基本概念をご覧ください。
シグナルとは何か?
シグナルは、値が変更されたときに興味のあるコンシューマーに通知する、値をラップしたものです。シグナルは、プリミティブから複雑なデータ構造まで、あらゆる値を含めることができます。
シグナルの値は、そのゲッター関数を呼び出すことで読み取ることができます。これは、Angularがシグナルがどこで使用されているかを追跡することを可能にします。
シグナルは、書き込み可能または読み取り専用のいずれかになります。
書き込み可能なシグナル
書き込み可能なシグナルは、値を直接更新するためのAPIを提供します。書き込み可能なシグナルは、シグナルの初期値を指定してsignal関数を呼び出すことで作成します。
const count = signal(0);// シグナルはゲッター関数です - 関数を呼び出すことで値を読み取ります。console.log('The count is: ' + count());
書き込み可能なシグナルの値を変更するには、.set()で直接設定します。
count.set(3);
または、.update()メソッドを使用して、前の値から新しい値を計算します。
// カウントを1増やす。count.update(value => value + 1);
書き込み可能なシグナルは、WritableSignalという型になります。
算出シグナル
算出シグナルは、他のシグナルから値を派生させる読み取り専用のシグナルです。算出シグナルは、computed関数を使用して、派生を指定することで定義します。
const count: WritableSignal<number> = signal(0);const doubleCount: Signal<number> = computed(() => count() * 2);
doubleCount シグナルは、count シグナルに依存しています。countが更新されるたびに、AngularはdoubleCountも更新する必要があることを認識します。
算出シグナルは、遅延評価とメモ化が行われる
doubleCountの派生関数は、最初にdoubleCountを読み取るまで、その値を計算するために実行されません。計算された値はキャッシュされ、doubleCountを再び読み取ると、再計算せずにキャッシュされた値が返されます。
その後、countを変更すると、AngularはdoubleCountのキャッシュされた値がもはや有効ではなくなり、次にdoubleCountを読み取るときに新しい値が計算されることを認識します。
その結果、配列のフィルタリングなど、計算量が多い派生を算出シグナルで安全に実行できます。
算出シグナルは、書き込み可能なシグナルではない
算出シグナルに値を直接割り当てることはできません。つまり、
doubleCount.set(3);
はコンパイルエラーになります。なぜなら、doubleCountはWritableSignalではないからです。
算出シグナルの依存関係は動的である
派生中に実際に読み取られたシグナルのみが追跡されます。たとえば、このcomputedでは、count シグナルはshowCount シグナルが真の場合にのみ読み取られます。
const showCount = signal(false);const count = signal(0);const conditionalCount = computed(() => { if (showCount()) { return `The count is ${count()}.`; } else { return 'Nothing to see here!'; }});
conditionalCountを読み取ると、showCountが偽の場合、count シグナルを読み取ることなく、「Nothing to see here!」というメッセージが返されます。これは、後でcountを更新しても、conditionalCountのキャッシュされた値は再計算されないことを意味します。
showCountを真に設定してconditionalCountを再び読み取ると、派生が再実行されます。showCountが真のブランチに移り、countの値を示すメッセージが返されます。その後、countを変更すると、conditionalCountのキャッシュされた値が無効になります。
依存関係は、派生中に追加されるだけでなく、削除されることもできます。後でshowCountを再び偽に設定すると、countはconditionalCountの依存関係として扱われなくなります。
高度な派生
computedはシンプルな読み取り専用の派生を処理しますが、他のシグナルに依存する書き込み可能な状態が必要な場合があります。
詳細については、linkedSignalによる依存状態ガイドを参照してください。
すべてのシグナルAPIは同期的です—signal、computed、inputなど。しかし、アプリケーションは非同期に利用可能なデータを扱う必要があることがよくあります。Resourceは、非同期データをアプリケーションのシグナルベースのコードに組み込み、そのデータに同期的にアクセスできるようにする方法を提供します。詳細については、リソースによる非同期リアクティビティガイドを参照してください。
非リアクティブAPIでの副作用の実行
状態の変更に反応したい場合、同期または非同期の派生が推奨されます。しかし、これがすべてのユースケースをカバーするわけではなく、非リアクティブAPIでシグナルの変更に反応する必要がある状況に遭遇することがあります。これらの特定のユースケースには、effectまたはafterRenderEffectを使用してください。詳細については、非リアクティブAPIの副作用ガイドを参照してください。
OnPushコンポーネントでのシグナルの読み取り
OnPushコンポーネントのテンプレート内でシグナルを読み取ると、Angularはシグナルをそのコンポーネントの依存関係として追跡します。そのシグナルの値が変更されると、Angularは自動的にコンポーネントをマークして、次に変更検知が実行されたとき更新されるようにします。OnPushコンポーネントの詳細については、コンポーネントのサブツリーをスキップするガイドを参照してください。
詳細なトピック
シグナルの等価関数
シグナルを作成する際には、オプションで等価関数を指定できます。これは、新しい値が前の値と実際に異なるかどうかを確認するために使用されます。
import _ from 'lodash';const data = signal(['test'], {equal: _.isEqual});// これは別の配列インスタンスですが、// 深い等価関数を使用することで値は等しいと判断され、// シグナルは更新をトリガーしません。data.set(['test']);
等価関数は、書き込み可能なシグナルと算出シグナルの両方に指定できます。
HELPFUL: デフォルトでは、シグナルは参照の等価性(Object.is() 比較)を使用します。
シグナルの型チェック
isSignalを使用して、値がSignalかどうかをチェックできます:
const count = signal(0);const doubled = computed(() => count() * 2);isSignal(count); // trueisSignal(doubled); // trueisSignal(42); // false
シグナルが書き込み可能かどうかを具体的にチェックするには、isWritableSignalを使用します:
const count = signal(0);const doubled = computed(() => count() * 2);isWritableSignal(count); // trueisWritableSignal(doubled); // false
依存関係を追跡せずに読み取る
まれに、computedやeffectなどのリアクティブ関数内でシグナルを読み取るコードを実行する必要があり、依存関係を作成しない場合があります。
たとえば、currentUserが変更されたときに、counterの値をログに記録する必要があるとします。両方のシグナルを読み取るeffectを作成できます。
effect(() => { console.log(`User set to ${currentUser()} and the counter is ${counter()}`);});
この例では、currentUserまたはcounterのいずれかが変更されると、メッセージがログに記録されます。しかし、currentUserのみが変更されたときにエフェクトを実行する必要がある場合、counterの読み取りは単なる付随的なものであり、counterが変更されても新しいメッセージはログに記録されるべきではありません。
シグナルのゲッターをuntrackedで呼び出すことで、シグナルの読み取りが追跡されないようにできます。
effect(() => { console.log(`User set to ${currentUser()} and the counter is ${untracked(counter)}`);});
untrackedは、エフェクトが、依存関係として扱われない外部のコードを呼び出す必要がある場合にも役立ちます。
effect(() => { const user = currentUser(); untracked(() => { // `loggingService`がSignalを読み取っても、 // このEffectの依存関係として扱われません。 this.loggingService.log(`User set to ${user}`); });});
RxJSとシグナルを併用する
シグナルとRxJSの相互運用性の詳細については、RxJSとAngularシグナルの相互運用 を参照してください。