Skip to content

Angularでのコンポーネント内からの入力シグナル値変更

問題点:
Angularのinput()で宣言された入力シグナルに対して、コンポーネント内で.set().update()を直接呼び出そうとしても動作しません。これは以下のエラーが発生する典型的なケースです:

typescript
// コンポーネント内での試行(エラー発生)
this.selected.set(true); // Property 'set' does not exist
this.selected().set(true); // 実行時エラー

根本原因:
Angularの入力シグナル(input())は設計上読み取り専用(read-only) です。親コンポーネントから受け取った値のみを反映し、子コンポーネント側からの変更は意図的に禁止されています。公式RFCでも明示されています:

"Inputs are read-only"(入力は読み取り専用)
Angular Signal RFC Discussion


適切な解決策

方法1: model() を使用した双方向バインディング(推奨)

Angular v17.2以降では、書き込み可能な双方向バインディング向けのmodel() APIが提供されています:

typescript
import { Component, model } from '@angular/core';

@Component({...})
export class YourComponent {
  // model()で宣言(初期値false)
  selected = model(false);

  toggleSelection() {
    // コンポーネント内での更新が可能
    this.selected.set(!this.selected());
  }
}

親コンポーネントでの使用例:

html
<your-component [(selected)]="parentValue" />
typescript
parentValue = signal(false); // 自動的に同期される

特徴:

  • model() は書き込み可能なシグナルを生成
  • [(selected)] 構文で双方向バインディングを実現
  • 値変更が親コンポーネントに自動通知される

方法2: computed() を使用した派生状態(読み取り専用が必要な場合)

直接変更できず単なる表示用の値が必要な場合の代替案:

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

selected = input(false);
private _localSelected = signal(false);

// 入力値と内部状態を合成
hasSelected = computed(() => 
  this.selected() || this._localSelected()
);

ユースケース:

  • 入力値を基にした読み取り専用の派生値が必要な場合
  • 内部状態と入力値を組み合わせたいとき

なぜ input() は書き込み不可なのか?

  1. データフローの明確化:
    親→子の一方通行を強制し、予期しない副作用を防止

  2. 変更検知の最適化:
    入力値の変更源を常に親コンポーネントに限定することでパフォーマンス向上

  3. デバッグ容易性:
    データ変更が常に親コンポーネント経由で発生するためトレースが容易

実運用でのアドバイス

  • 双方向バインディングが必要な場合: 常にmodel()を選択
  • 単なる入力値の受け取りだけ: input()が適切
  • Angularバージョン: v17.2未満ではcomputed()を一時的に使用し、早期バージョンアップを推奨

関連リソース: