【Angular基礎講座】Signals徹底解説!リアクティブな状態管理の新しい波に乗ろう


※ 当ページには【広告/PR】を含む場合があります。
2025/07/13
Angular/SSRでブラウザにあってNodejsにないJavascript APIクラスを使う際に気を使うこと
【Angular/SSRでSEO対策】Angular/SSRサイトのURL変更時に役立つ301リダイレクト設定
蛸壺の技術ブログ|Signals徹底解説!リアクティブな状態管理の新しい波に乗ろう

Angularに
「Signals」が導入されてしばらく経ちました。SolidJSやReactJSといった他のフレームワークでは先行して同様の設計思想が取り入れられていたため、それらの経験がある方には馴染みやすいかもしれません。

しかし、これまでAngular一筋で開発されてきた方にとっては、その概念や使い方に戸惑うこともあるのではないでしょうか。

次期Angularでは
「zoneless」という、Zone.jsに依存しない変更検知の仕組みが本格的に導入される波が来ています。これに伴い、既存のコードベースをSignalsベースに移行することが、今後必須となる可能性も十分に考えられます。

この記事では、Angular Signalsの基本的な使い方から、そのメリット、そして具体的な実装例までを、できるだけ分かりやすく解説していきます。Signalsを理解し、今後のAngular開発に役立てるための一助となれば幸いです。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート・2025年最新】Angular(JSフレームワーク)をこれから学びたい人のためのオススメ書籍&教材特集

AngularでSignalsを使うメリットについて

Angular Signalsは、アプリケーションの状態管理を最適化し、変更検知の効率を大幅に向上させるための強力なメカニズムです。

では、具体的にどのようなメリットがあるか以降で説明していきます。

Signalsの概要と有用性

Signalsは、値が変更されたときに、その値に関心のあるコンシューマーに通知する「値のラッパー」です。

プリミティブな値から複雑なデータ構造まで、あらゆる値を格納できます。Signalsの値を読み取る際には、そのゲッター関数を呼び出すことで、Angularはどこでそのシグナルが使用されているかを追跡し、必要な部分のみを効率的に更新できるようになります。

主なメリットとしては、以下の点が挙げられます。

            *   **きめ細やかな変更検知**:
    Signalsは、値の変更を非常に細かく追跡します。
    これにより、アプリケーション全体で不要な再レンダリングや再計算を防ぎ、パフォーマンスを向上させることができます。
*   **リアクティブなプログラミングの簡素化**:
    RxJSのような複雑なストリーム操作を必要とせず、
    より直感的かつ宣言的な方法でリアクティブな状態管理を記述できます。
*   **Zone.jsからの脱却**:
    将来的な「zoneless」アプリケーションへの移行を見据えた基盤となります。
    Zone.jsのオーバーヘッドを削減し、より高速なアプリケーションの実現に貢献します。
*   **明確な依存関係**:
    シグナル間の依存関係が明確になり、コードの可読性と保守性が向上します。
        

Signalsのコア機能

Angular Signalsには、主に以下の3つのコア機能があります。

            1.  **Writable Signals (`signal`関数)**:
    値を直接更新できるシグナルです。
    初期値を`signal`関数に渡して作成し、
    `.set()`メソッドで直接値を設定したり、
    `.update()`メソッドで以前の値から新しい値を計算して設定したりできます。

2.  **Computed Signals (`computed`関数)**:
    他のシグナルから値を派生させる「読み取り専用」のシグナルです。
    `computed`関数と、値を派生させるロジックを指定して定義します。
    遅延評価され、メモ化されるため、計算コストの高い派生処理も効率的に実行できます。

3.  **Effects (`effect`関数)**:
    1つ以上のシグナル値が変更されるたびに実行される操作です。
    常に少なくとも一度は実行され、
    その中で読み取られたシグナル値を追跡し、
    それらの値が変更されるたびに再度実行されます。
    主に、表示されているデータのログ記録や`window.localStorage`とのデータ同期など、
    副作用を伴う処理に利用されます。
        

合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート・2025年最新】Angular(JSフレームワーク)をこれから学びたい人のためのオススメ書籍&教材特集

Signalsの基本構文・主要なメソッドの使い方

それでは、Angular Signalsの基本的な構文と主要なメソッドの使い方を見ていきましょう。

`signal`関数:書き込み可能シグナル

signal関数は、値を直接更新できる最も基本的なシグナルです。

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

//初期値0でシグナルを作成します
const count = signal(0);

//シグナルの値を読み取るには、ゲッター関数として呼び出します
//👇出力 ... 現在のカウントは: 0
console.log('現在のカウントは: ' + count());

//set()メソッドで値を直接変更
count.set(3);
//👇出力 ... カウントは3になりました: 3
console.log('カウントは3になりました: ' + count());

//update()メソッドで以前の値から新しい値を計算して変更
count.update(value => value + 1);
//👇出力 ... 最終的なカウントは: 4
console.log('最終的なカウントは: ' + count());
        

`computed`関数:算出シグナル

computed関数は、他のシグナルから値を派生させる読み取り専用のシグナルです。依存するシグナルが変更された場合にのみ再計算され、パフォーマンスが最適化されます。

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

const count = signal(0);
//countシグナルに依存する算出シグナルを作成
const doubleCount = computed(() => count() * 2);

//👇出力 ...  0 (初めて読み込まれたときに計算される)
console.log(doubleCount());

count.set(5);
//👇出力 ...  10 (countが変更されたため再計算)
console.log(doubleCount());

const showCount = signal(false);
const conditionalCount = computed(() => {
  if (showCount()) {
    return `カウントは ${count()} です。`;
  } else {
    return '何も表示されていません!';
  }
});

//👇出力 ...  '何も表示されていません!'
//※(showCountがfalseのためcountは読み取られない)
console.log(conditionalCount());

//conditionalCountは再計算されない
count.set(10);
//👇出力 ... '何も表示されていません!'
console.log(conditionalCount());

showCount.set(true);
//👇出力 ... 'カウントは 10 です。'
//※(showCountがtrueになり、countが読み取られる)
console.log(conditionalCount());

//conditionalCountは再計算されるようになる
count.set(15);
//👇出力 ...  'カウントは 15 です。'
console.log(conditionalCount());
        

`effect`関数:副作用の実行

effect関数は、1つ以上のシグナル値が変更されるたびに実行される操作です。主に、DOM操作やログ出力、外部システムとの同期など、副作用を伴う処理に使用されます。

            import {
    signal, effect, untracked,
    Injector, inject
} from '@angular/core';
import { Component } from '@angular/core';

@Component({
    selector: 'app-counter',
    template: ``,
    standalone: true
})
export class CounterComponent {
    readonly count = signal(0);
    private readonly user = signal('Alice');

    //コンストラクター内でエフェクトを作成するのが一般的
    constructor() {
        effect(() => {
            console.log(`現在のカウントは: ${this.count()}`);
        });

        //依存関係を追跡せずにシグナルを読み込む例:
        //userシグナルが変更されたときにのみエフェクトが実行される
        effect(() => {
            console.log(`ユーザーは ${this.user()} に設定され、カウントは ${untracked(this.count)} です`);
        });

        //クリーンアップ関数を持つエフェクトの例:
        //エフェクトが再実行されるか破棄される前に、タイマーなどのリソースをクリア
        effect((onCleanup) => {
            const currentUserValue = this.user();
            const timer = setTimeout(() => {
                console.log(`1秒前、ユーザーは ${currentUserValue} になりました`);
            }, 1000);
            onCleanup(() => {
                //エフェクトが再実行されるか破棄される前にタイマーをクリア
                clearTimeout(timer);
            });
        });
    }

    //インジェクターを使用してコンストラクター外でエフェクトを作成する例
    private injector = inject(Injector);
    initializeLogging(): void {
        effect(() => {
            console.log(`カウントのログが初期化されました: ${this.count()}`);
        }, { injector: this.injector });
    }

    increment() {
        this.count.update(value => value + 1);
    }

    changeUser(newUser: string) {
        this.user.set(newUser);
    }
}

//シグナルの変更をトリガーしてエフェクトの動作を確認
const componentInstance = new CounterComponent();

//エフェクトが実行
componentInstance.increment();
//ユーザー変更エフェクトとクリーンアップエフェクトが実行
componentInstance.changeUser('Bob');
        

合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート・2025年最新】Angular(JSフレームワーク)をこれから学びたい人のためのオススメ書籍&教材特集

応用的な実装例

ここからは、Signalsのより実践的な応用例を見ていきましょう。

Signalの実装の基本

前述の基本構文で示した
signalcomputedeffectの組み合わせは、Angularアプリケーションのリアクティブな状態管理の基盤となります。これらを適切に組み合わせることで、UIの状態やビジネスロジックを効率的に記述できます。

例えば、ユーザーが入力したテキストに基づいて、そのテキストの長さや特定のキーワードが含まれているかをリアルタイムで表示するような機能は、これらのシグナルを組み合わせることで簡単に実現できます。

Signalの応用

Signalsは、より複雑なシナリオにも対応できるよう、
linkedSignalresourceといった高度な機能も提供しています。

`linkedSignal`:他の状態に連動するシグナル

linkedSignal関数は、本質的に他の状態に「リンク」している状態を保持するためのシグナルを作成します。

通常の
signalとは異なり、デフォルト値を渡す代わりに、算出関数を渡します。この算出関数によって値が変更されると、linkedSignalの値も算出結果に更新され、常に有効な値を保持するのに役立ちます。

特に、あるデータの整合性が別のデータに強く依存するような状況で非常に有用です。

例えば、配送オプションのリストが変更された際に、現在選択されているオプションが新しいリストにも存在すればその選択を維持し、そうでなければリストの最初のオプションにデフォルト設定するといったユースケースが考えられます。

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

interface ShippingMethod {
    id: number;
    name: string;
}

const shippingOptions = signal<ShippingMethod[]>([
    { id: 0, name: 'Ground' },
    { id: 1, name: 'Air' },
    { id: 2, name: 'Sea' },
]);

//基本的なlinkedSignalの例 (常に最初のオプションにリンク)
const selectedOptionBasic = linkedSignal(() => shippingOptions());

//👇出力 ...  { id: 0, name: 'Ground' }
console.log(selectedOptionBasic());

shippingOptions.set([
    { id: 0, name: 'Email' },
    { id: 1, name: 'Will Call' },
]);
//👇出力 ...  { id: 0, name: 'Email' }
console.log(selectedOptionBasic());

//以前の状態を考慮するlinkedSignalの例:
//新しいオプションリストに以前選択されていたオプションがあればそれを維持し、
// なければ、リストの最初のオプションをデフォルトを返す
const selectedOptionAdvanced = linkedSignal<ShippingMethod[], ShippingMethod>({
    source: shippingOptions,
    computation: (newOptions, previous) => {
        return (
            newOptions.find(opt => opt.id === previous?.value.id) ?? newOptions[0]
        );
    },
});

//テスト
shippingOptions.set([
    { id: 0, name: 'Ground' },
    { id: 1, name: 'Air' },
    { id: 2, name: 'Sea' },
]);
//'Sea' を選択
selectedOptionAdvanced.set(shippingOptions()[2]);
//👇出力 ...  { id: 2, name: 'Sea' }
console.log("初期選択:", selectedOptionAdvanced());

//配送オプションを変更します
shippingOptions.set([
    { id: 0, name: 'Email' },
    { id: 1, name: 'Sea' }, //'Sea'は新しいリストにも存在
    { id: 2, name: 'Postal Service' },
]);
//出力 ... { id: 1, name: 'Sea' } (以前の選択が維持される)
console.log("オプション変更後 (一致あり):", selectedOptionAdvanced());

//配送オプションをさらに変更 ('Sea'が存在しないリスト)
shippingOptions.set([
    { id: 0, name: 'Standard' },
    { id: 1, name: 'Express' },
]);
//出力 ... { id: 0, name: 'Standard' } (最初のオプションにデフォルト設定される)
console.log("オプション変更後 (一致なし):", selectedOptionAdvanced());
        

合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート・2025年最新】Angular(JSフレームワーク)をこれから学びたい人のためのオススメ書籍&教材特集

まとめ

以上、AngularのSignalsの概要と利用法についてまとめてみました。

これまで作り込んできたAngularプロジェクトをすぐさま「zoneless」へ移行しなくてはいけないわけではないのですが、今後zone.jsがサポートされないときが来るときに備えて、コツコツと移行の準備をしておきましょう。