Dev Study
← 解説「リアクティブフォーム — FormControl と FormGroup」に戻る

サンプルコードで身につける: リアクティブフォーム — FormControl と FormGroup

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

1最小の FormControl

リアクティブフォームの最小単位は、1つの入力欄を表す FormControl です。new FormControl('') で作り、value で現在の値を読み、setValue() でプログラムから値を設定できます。フォームの値がTypeScript側のオブジェクトとして手元にある、という感覚をまずこの1つで掴んでください。

import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-nickname',
  imports: [ReactiveFormsModule],
  // [formControl] で入力欄と FormControl を結び付ける
  template:
    '<input [formControl]="nickname" placeholder="ニックネーム">' +
    '<p>入力中の値: {{ nickname.value }}</p>',
})
export class NicknameComponent {
  nickname = new FormControl('');

  reset() {
    // プログラム側からも値を設定できる
    this.nickname.setValue('ゲスト');
  }
}

2FormGroup でログインフォームを組み立てる

複数の FormControl は FormGroup でひとまとめにし、1つのフォームとして扱います。テンプレートでは <form [formGroup]="form"> で全体を、formControlName="email" で各入力欄を結び付けます。ログインフォームのような「複数項目で1つの意味を持つ」入力は、この形が基本形です。

import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-login',
  imports: [ReactiveFormsModule],
  // [formGroup] でフォーム全体、formControlName で各項目を対応させる
  template:
    '<form [formGroup]="form" (ngSubmit)="login()">' +
    '  <input formControlName="email" placeholder="メール">' +
    '  <input formControlName="password" type="password">' +
    '  <button type="submit">ログイン</button>' +
    '</form>',
})
export class LoginComponent {
  form = new FormGroup({
    email: new FormControl(''),
    password: new FormControl(''),
  });

  login() {
    console.log('送信内容:', this.form.value);
  }
}

3値の取得 — value と getRawValue の違い

フォームの値は form.value でいつでもオブジェクトとして取り出せます。ただし disable() で無効化したコントロールは value に含まれなくなるため、無効な項目も含めた全部の値が欲しいときは getRawValue() を使います。「画面上は編集不可だが送信には含めたい」項目があるフォームで、この違いが効いてきます。

import { FormControl, FormGroup } from '@angular/forms';

const form = new FormGroup({
  name: new FormControl('田中'),
  plan: new FormControl('premium'),
});

// plan を編集不可にする(契約プランは画面から変更させない、など)
form.get('plan')?.disable();

// value は無効化したコントロールを含まない
console.log(form.value);
// => { name: '田中' }

// getRawValue() は無効化したものも含めた全項目を返す
console.log(form.getRawValue());
// => { name: '田中', plan: 'premium' }

4valueChanges で入力の変化を購読する

各コントロールやフォーム全体は valueChanges という Observable を持ち、値が変わるたびに新しい値が流れてきます。subscribe すると「入力に反応して何かする」処理をクラス側に書けます。検索キーワードの入力に合わせて候補を出す、といったリアルタイムな挙動の土台になります。

import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-search-box',
  imports: [ReactiveFormsModule],
  template: '<input [formControl]="keyword" placeholder="検索">',
})
export class SearchBoxComponent {
  keyword = new FormControl('');

  constructor() {
    // 値が変わるたびに新しい値が流れてくる
    this.keyword.valueChanges.subscribe((value) => {
      console.log('入力が変わりました:', value);
      // ここで検索候補の取得などを行う
    });
  }
}

5setValue と patchValue の違い

FormGroup へまとめて値を入れる方法は2つあります。setValue() は全項目を漏れなく渡す必要があり、1つでも欠けるとエラーになる厳密な方式です。patchValue() は渡した項目だけを部分的に更新できるので、「サーバーから取った一部の値だけ反映したい」編集画面などで便利です。

import { FormControl, FormGroup } from '@angular/forms';

const form = new FormGroup({
  name: new FormControl(''),
  email: new FormControl(''),
});

// setValue は全項目を渡す。欠けているとエラーになる
form.setValue({ name: '佐藤', email: 'sato@example.com' });

// patchValue は一部だけの更新ができる
form.patchValue({ name: '鈴木' });

console.log(form.value);
// => { name: '鈴木', email: 'sato@example.com' }

// form.setValue({ name: '高橋' });
// => email が無いので実行時エラーになる