← 解説「model() — 親と子で値を双方向に同期する」に戻る
サンプルコードで身につける: model() — 親と子で値を双方向に同期する
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1読み書きできる入力を宣言する
model の最小例で、value は親から受け取れるうえに、子からも update で書き換えられます。input と違って子側に書き込みAPIがあることが最大の違いです。親は [(value)] と書くだけで、子の変更が自動で親へ伝わります。
import { Component, model } from '@angular/core';
@Component({
selector: 'app-counter-input',
template:
'<button (click)="add()">+1</button> 現在: {{ value() }}',
})
export class CounterInputComponent {
// 親と双方向に同期する。子からも書き換えられる
value = model(0);
add() {
this.value.update((v) => v + 1); // 親側の値も一緒に更新される
}
}
// 親側: <app-counter-input [(value)]="count" />
2開閉状態を親と共有するパネル
「開く・閉じる」の操作は子の中で完結しつつ、現在開いているかどうかは親も知りたい、という model 向きの典型例です。真偽値の model を toggle で反転させるだけで、親の変数も同期されます。アコーディオンやサイドメニューの開閉でよく見る形です。
import { Component, model } from '@angular/core';
@Component({
selector: 'app-collapsible',
template:
'<button (click)="toggle()">詳細を表示</button>' +
'@if (opened()) { <p>ここに詳細が表示されます</p> }',
})
export class CollapsibleComponent {
opened = model(false);
toggle() {
this.opened.update((v) => !v); // 開閉のたびに親へも伝わる
}
}
// 親側: <app-collapsible [(opened)]="isDetailOpen" />
3テキスト編集部品を作る
入力欄の値を model で持つと、「親が初期値を渡し、ユーザーの編集が親へ戻る」というフォーム部品の基本形が作れます。[value] で表示し、(input) で set する、というシグナルとイベントの組み合わせがそのまま使えます。ニックネームやタイトルの編集欄など、小さな入力部品の定番です。
import { Component, model } from '@angular/core';
@Component({
selector: 'app-nickname-editor',
template:
'<input [value]="nickname()" (input)="onInput($event)">' +
'<p>プレビュー: {{ nickname() }}</p>',
})
export class NicknameEditorComponent {
nickname = model('');
onInput(e: Event) {
this.nickname.set((e.target as HTMLInputElement).value);
}
}
// 親側: <app-nickname-editor [(nickname)]="displayName" />
4親子がつながる様子を1画面で見る
星評価の子と、それを使うレビューフォームの親をセットで見せる例です。子が stars を増やすと、親の reviewScore も同じ値になっていることがテンプレートの表示で確認できます。[( )] という記法が input と output の組を1つにまとめた省略形であることが実感できます。
import { Component, model, signal } from '@angular/core';
@Component({
selector: 'app-star-rating',
template: '<button (click)="addStar()">★を増やす</button> {{ stars() }}',
})
export class StarRatingComponent {
stars = model(0);
addStar() {
this.stars.update((v) => v + 1);
}
}
@Component({
selector: 'app-review-form',
imports: [StarRatingComponent],
// [( )] で親の reviewScore と子の stars がつながる
template:
'<app-star-rating [(stars)]="reviewScore" />' +
'<p>あなたの評価: {{ reviewScore() }}</p>',
})
export class ReviewFormComponent {
reviewScore = signal(0);
}
5上限付きの数量ステッパー
カートの数量変更を想定した実務例で、model の数量と input の在庫上限を組み合わせています。1未満や在庫超えにならないよう update の中で範囲を制限しつつ、確定した値は親と常に同期されます。「値の編集が主役の部品に model を使う」という使いどころの判断基準も体現しています。
import { Component, input, model } from '@angular/core';
@Component({
selector: 'app-quantity-stepper',
template:
'<button (click)="step(-1)">-</button>' +
' {{ quantity() }} 個 ' +
'<button (click)="step(1)">+</button>',
})
export class QuantityStepperComponent {
quantity = model(1); // 親と同期する数量
maxStock = input(99); // 在庫の上限は親から受け取る
step(diff: number) {
// 1個未満・在庫数超えにならない範囲で更新する
this.quantity.update((v) =>
Math.min(this.maxStock(), Math.max(1, v + diff)),
);
}
}
// 親側: <app-quantity-stepper [(quantity)]="cartQuantity" [maxStock]="stock" />