Dev Study
Angular コース

32. シグナルフォーム — form() でフォームもシグナルに

v22で正式版になったシグナルフォームは、フォームの値をシグナルのモデルとして持ち、form() でそこからフォームを組み立てる新しい方式です。signal({ email: '', password: '' }) のようにモデルを作り、form(model, schema) に渡すと、各項目に対応したフィールドのツリーができます。これまで学んだリアクティブフォーム(FormGroup / FormControl)の、シグナル時代版だと考えると分かりやすいです。

検証は form() の第2引数に渡すスキーマ関数の中で宣言します。required(path.email) や email(path.email) のように「どの項目にどの検証を効かせるか」を書くと、値が変わるたびに自動で再判定されます。テンプレートでは入力欄に [formField]="loginForm.email" をつないで双方向に同期し、loginForm.email().errors() や loginForm.invalid() でエラーや全体の妥当性をシグナルとして読み取れます。

リアクティブフォームと違い、値もエラーも妥当性もすべてシグナルなので、computedやeffectとそのまま組み合わせられるのが利点です。新規開発のフォームはシグナルフォームが第一候補になりますが、既存のリアクティブフォームも引き続き使えるので、無理に全部を書き換える必要はありません。

サンプルコード(フレームワーク環境が必要なため表示のみ)

import { Component, signal } from '@angular/core';
import { form, FormField, required, email } from '@angular/forms/signals';

@Component({
  selector: 'app-login-form',
  imports: [FormField],
  // [formField] で入力欄とフィールドを双方向に同期する
  template:
    '<input type="email" [formField]="loginForm.email">' +
    '@if (loginForm.email().touched() && loginForm.email().invalid()) {' +
    '  @for (e of loginForm.email().errors(); track e) { <p>{{ e.message }}</p> }' +
    '}' +
    '<button [disabled]="loginForm().invalid()">送信</button>',
})
export class LoginFormComponent {
  // フォームの中身はただのシグナルのモデル
  model = signal({ email: '', password: '' });

  // スキーマ関数の中で、どの項目にどの検証を効かせるかを宣言する
  loginForm = form(this.model, (path) => {
    required(path.email, { message: 'メールは必須です' });
    email(path.email, { message: 'メール形式で入力してください' });
    required(path.password, { message: 'パスワードは必須です' });
  });
}
公式ドキュメントで詳しく ↗