Dev Study
← 解説「linkedSignal — 連動して作り直せる書き込み可能な状態」に戻る

サンプルコードで身につける: linkedSignal — 連動して作り直せる書き込み可能な状態

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

1signal・computed・linkedSignal の位置づけ

3つの道具の違いは「値がどこから来るか」と「上書きできるか」で整理できます。linkedSignalは両方の性質を持つ中間の存在です。この表を頭に入れると使い分けに迷いません。

// ── 3つの状態の道具の使い分け ──
//
//               値の出どころ         set()で上書き
// signal        自分で入れる          できる
// computed      他のシグナルから計算   できない
// linkedSignal  他のシグナルから計算   できる ★中間
//
// linkedSignal = 「基本は計算で決まるが、
//                依存が変わるまでは上書きが生きる」状態

2リストが変わると選択がリセットされる流れ

カテゴリを切り替えると商品リストが変わり、選択中の商品が自動で先頭に戻ります。ユーザーが選び直した値は、次にリストが変わるまで保持されます。この一連の動きをコメントで追ってみます。

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

const items = signal(['りんご', 'みかん']);
const selected = linkedSignal(() => items()[0]);

console.log(selected()); // 'りんご'(計算結果)

selected.set('みかん');
console.log(selected()); // 'みかん'(上書きが生きている)

items.set(['牛乳', 'パン']); // 依存が変わった!
console.log(selected()); // '牛乳'(計算し直されてリセット)

3source と computation を分けた書き方

linkedSignalにはオブジェクト形式もあり、監視する source と計算の computation を分けて書けます。computation は前回の値も受け取れるので、「新しいリストにも前の選択が残っていれば維持する」といった賢いリセットが書けます。

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

const items = signal(['りんご', 'みかん', 'ぶどう']);

const selected = linkedSignal({
  source: items,
  computation: (newItems, previous) => {
    // 前回の選択が新リストにも入っていれば、それを維持
    if (previous && newItems.includes(previous.value)) {
      return previous.value;
    }
    return newItems[0]; // 無ければ先頭にリセット
  },
});

selected.set('みかん');
items.set(['みかん', 'バナナ']); // みかんは残っている
console.log(selected()); // 'みかん'(維持された)

4effect で書くとアンチパターンになる例

linkedSignalが無い時代は、signalとeffectを組み合わせて同じ動きを作っていました。状態の定義とリセットのロジックが離れてしまい、追いかけにくいコードになります。Before/Afterで比べてみましょう。

// ── Before: signal + effect(追いにくい)──
// selected = signal<string | undefined>(undefined);
// constructor() {
//   effect(() => {
//     // リセットの理由がここに埋もれる。書き込み経路も2本になる
//     this.selected.set(this.items()[0]);
//   });
// }

// ── After: linkedSignal(1行で意図が見える)──
// selected = linkedSignal(() => this.items()[0]);
//
// 「itemsが変わったら先頭に戻る、それ以外は上書きが生きる」
// という仕様が、定義の1行だけで読み取れる

5編集フォームの下書きに使う

「選択中のユーザーが変わったら入力欄を元データに戻す。ただし編集中の値は保持する」という編集画面の定番要件も、linkedSignalそのものです。resource()やhttpResource()の取得結果と組み合わせても同じ形になります。

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

type User = { id: number; name: string };

@Component({
  selector: 'app-user-editor',
  template:
    '<input [value]="draftName()" ' +
    '       (input)="draftName.set($any($event.target).value)">',
})
export class UserEditorComponent {
  user = signal<User>({ id: 1, name: '松本' });

  // 編集用の下書き。user が別人に変わったら元データに戻る
  draftName = linkedSignal(() => this.user().name);

  save() {
    console.log('保存する名前:', this.draftName());
  }
}