Dev Study
← 解説「computed — シグナルから作る派生値」に戻る

サンプルコードで身につける: computed — シグナルから作る派生値

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

11つのシグナルから2倍の値を作る

computed の最小例で、count が変わるたびに doubled も自動で新しい値になります。doubled 自身は set できない読み取り専用で、値の出どころが count だけに限定されるのがポイントです。「元の値と派生値」という関係の出発点です。

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

@Component({
  selector: 'app-double-view',
  template:
    '<p>{{ count() }} の2倍は {{ doubled() }}</p>' +
    '<button (click)="add()">+1</button>',
})
export class DoubleViewComponent {
  count = signal(1);
  // count が変わると doubled も自動で変わる(読み取り専用)
  doubled = computed(() => this.count() * 2);

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

2複数のシグナルから合計金額を導く

computed は複数のシグナルに依存でき、どれか1つでも変われば再計算されます。単価と個数から合計を導く、というカートの定番計算で「合計を自分で更新するコードがどこにもない」ことに注目してください。更新するのは元の値だけ、が computed 設計の鉄則です。

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

@Component({
  selector: 'app-cart-total',
  template:
    '<p>単価 {{ unitPrice() }} 円 × {{ quantity() }} 個</p>' +
    '<p>合計: {{ total() }} 円</p>' +
    '<button (click)="addOne()">1個追加</button>',
})
export class CartTotalComponent {
  unitPrice = signal(300);
  quantity = signal(2);
  // どちらが変わっても合計は自動で再計算される
  total = computed(() => this.unitPrice() * this.quantity());

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

3computed の仕組みを自作して動かす

「依存するシグナルの変更を購読して、計算結果を作り直す」という computed の核を、プレーンなTypeScriptで体験する例です。quantity を書き換えただけで total の読み出し結果が変わる様子を、実際に実行して確認できます。派生値を手で更新しなくてよい理由がこの数行に詰まっています。

TypeScript

4送信可能かどうかを派生値にする

「入力が空でなければ送信できる」という真偽値も、元の入力シグナルから導ける立派な派生値です。canSubmit をボタンの [disabled] に直結することで、入力のたびにボタンの状態が自動で切り替わります。フォームのバリデーション表示は computed の代表的な実務ユースケースです。

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

@Component({
  selector: 'app-feedback-form',
  template:
    '<input (input)="onInput($event)" placeholder="ご意見をどうぞ">' +
    '<button [disabled]="!canSubmit()">送信</button>',
})
export class FeedbackFormComponent {
  comment = signal('');
  // 「空でないか」という真偽値も派生値として導ける
  canSubmit = computed(() => this.comment().trim().length > 0);

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

5キーワードで一覧を絞り込む

「絞り込み後の一覧」を computed で持つ、実務の検索・フィルタUIの定番構成です。キーワードと元データのどちらが変わっても一覧が自動で作り直され、絞り込みロジックはこの1箇所に集約されます。@for にはこの computed をそのまま渡すだけで済みます。

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

@Component({
  selector: 'app-fruit-filter',
  template:
    '<input (input)="onInput($event)" placeholder="名前で絞り込み">' +
    '<ul>@for (fruit of filtered(); track fruit) { <li>{{ fruit }}</li> }</ul>',
})
export class FruitFilterComponent {
  keyword = signal('');
  fruits = signal(['りんご', 'りんごジュース', 'バナナ', 'みかん']);
  // 元データとキーワードのどちらが変わっても自動で作り直される
  filtered = computed(() =>
    this.fruits().filter((f) => f.includes(this.keyword())),
  );

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