Dev Study
← 解説「effect — シグナルの変更に反応する」に戻る

サンプルコードで身につける: effect — シグナルの変更に反応する

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

1シグナルの変化をログに出す

effect の最小例で、コンストラクタの中に登録した関数が count の変更のたびに再実行されます。どのシグナルを追跡するかを自分で指定していないのに、関数の中で読んでいる count が自動で追跡される点が核心です。まずは console.log で再実行のタイミングを観察するのが理解の近道です。

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

@Component({
  selector: 'app-counter-logger',
  template: '<button (click)="add()">+1</button>',
})
export class CounterLoggerComponent {
  count = signal(0);

  constructor() {
    // 中で読んでいる count が自動で追跡される
    effect(() => {
      console.log('現在のカウント:', this.count());
    });
  }

  add() {
    this.count.update((v) => v + 1);
  }
}

2effect の仕組みを自作して動かす

「登録した瞬間にまず1回実行し、依存が変わるたびに再実行する」という effect の動きを、プレーンなTypeScriptで再現した例です。実行すると、登録直後に1回、write のたびに1回ずつ関数が動くことを確認できます。この「最初に1回」は実際のAngularのeffectでも同じ挙動です。

TypeScript

3下書きを localStorage に自動保存する

「シグナルの値が変わったらブラウザのストレージに保存する」という、effect の代表的な実務ユースケースです。localStorage はAngularの外の世界なので、テンプレートではなく effect が橋渡し役になります。エディタの下書き自動保存や設定の永続化はこのパターンで実装します。

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

@Component({
  selector: 'app-draft-saver',
  template: '<input (input)="onInput($event)" placeholder="下書き">',
})
export class DraftSaverComponent {
  draft = signal('');

  constructor() {
    // 画面の外(localStorage)への橋渡しはeffectの出番
    effect(() => {
      localStorage.setItem('draft', this.draft());
    });
  }

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

4未読件数をブラウザのタブに反映する

document.title はテンプレートの外にあるため、シグナルの変更を届けるには effect を使います。未読が増えるたびにタブの表示が「受信箱 (3)」のように更新される、メールやチャットアプリでおなじみの動きです。「テンプレートで描画できないものへの反映」という effect の守備範囲がよく分かる例です。

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

@Component({
  selector: 'app-unread-title',
  template: '<button (click)="receive()">新着メールを受信</button>',
})
export class UnreadTitleComponent {
  unreadCount = signal(0);

  constructor() {
    // ブラウザのタブ表示というAngularの外の世界へ変更を届ける
    effect(() => {
      document.title = '受信箱 (' + this.unreadCount() + ')';
    });
  }

  receive() {
    this.unreadCount.update((v) => v + 1);
  }
}

5検索の確定を分析基盤へ送る

「検索が確定するたびに分析イベントを送る」という、実務のログ収集を想定した例です。送信処理を effect に寄せることで、検索を実行するコードがどこに増えても記録漏れが起きません。初期値の空文字では送らない、というガード条件の置き方も実務でよく使う形です。

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

declare function sendAnalytics(eventName: string, data: object): void;

@Component({
  selector: 'app-search-tracker',
  template: '<button (click)="search()">りんごを検索</button>',
})
export class SearchTrackerComponent {
  submitted = signal('');

  constructor() {
    effect(() => {
      // 初期値の空文字では送らない、というガードを置く
      if (this.submitted() !== '') {
        sendAnalytics('search', { keyword: this.submitted() });
      }
    });
  }

  search() {
    this.submitted.set('りんご'); // 実際は入力欄の値を渡す
  }
}