はじめに
Angular技術要素解説シリーズ、第7弾です。
今回は、ユーザー入力を扱うフォームの作り方を解説します。
Angularには「Template-driven Forms」「Reactive Forms」という2つの方式があり、さらにAngular 22で「Signal Forms」が安定版として加わりました。
それぞれの違いを整理します。
この記事で扱う技術要素
- Template-driven Forms(テンプレート駆動フォーム)
- Reactive Forms(リアクティブフォーム)
- FormControl / FormGroup / FormArray
- バリデーション(Validators)
- Signal Forms
Template-driven Forms(テンプレート駆動フォーム)
シンプルなフォームに向いている方式で、以前解説した双方向バインディング([(ngModel)])を軸に構築します。
import { Component } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';
@Component({
selector: 'app-contact-form',
standalone: true,
imports: [FormsModule],
template: `
<form #contactForm="ngForm" (ngSubmit)="onSubmit(contactForm)">
<label>
お名前:
<input name="name" [(ngModel)]="formData.name" required minlength="2">
</label>
<label>
メールアドレス:
<input name="email" [(ngModel)]="formData.email" required email>
</label>
<button type="submit" [disabled]="contactForm.invalid">送信</button>
</form>
@if (contactForm.submitted) {
<p>送信内容: {{ formData.name }} 様 ({{ formData.email }})</p>
}
`,
})
export class ContactFormComponent {
formData = {
name: '',
email: '',
};
onSubmit(form: NgForm) {
if (form.valid) {
console.log('送信されたデータ:', this.formData);
}
}
}
required・minlength・emailといった、通常のHTML標準属性のような書き方でバリデーションルールを指定できるのが特徴です。
#contactForm="ngForm"のようにテンプレート参照変数を使うことで、フォーム全体の状態(invalid・submittedなど)をテンプレート内から直接参照できます。
手軽に書ける反面、複雑な条件分岐を伴うバリデーションや、動的にフィールドを増減させるような高度な制御はやや苦手です。
Reactive Forms(リアクティブフォーム)
大規模・複雑なフォームに向いている方式で、フォームの構造をTypeScript側で明示的に定義します。
FormControl:単一の入力項目
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-name-field',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<input [formControl]="nameControl">
@if (nameControl.invalid && nameControl.touched) {
<p class="error">2文字以上で入力してください</p>
}
`,
})
export class NameFieldComponent {
nameControl = new FormControl('', [Validators.required, Validators.minLength(2)]);
}
FormGroup:複数の項目をまとめる
import { Component } from '@angular/core';
import { FormGroup, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-signup-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
<label>
お名前:
<input formControlName="name">
@if (signupForm.get('name')?.invalid && signupForm.get('name')?.touched) {
<span class="error">お名前は必須です</span>
}
</label>
<label>
パスワード:
<input type="password" formControlName="password">
@if (signupForm.get('password')?.hasError('minlength')) {
<span class="error">8文字以上で入力してください</span>
}
</label>
<button type="submit" [disabled]="signupForm.invalid">登録</button>
</form>
`,
})
export class SignupFormComponent {
signupForm = new FormGroup({
name: new FormControl('', [Validators.required]),
password: new FormControl('', [Validators.required, Validators.minLength(8)]),
});
onSubmit() {
if (this.signupForm.valid) {
console.log('登録データ:', this.signupForm.value);
// { name: "...", password: "..." }
}
}
}
Reactive Formsでは、フォームの構造(どんな項目があり、どんなバリデーションが付いているか)がTypeScript側の1箇所に明示的に定義されるため、複雑なフォームでも見通しが良く、単体テストも書きやすくなります。
FormArray:可変長の項目群
「電話番号を複数登録できる」ような、項目数が動的に増減するフォームにはFormArrayを使います。
import { Component } from '@angular/core';
import { FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-phone-list-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form">
<div formArrayName="phoneNumbers">
@for (control of phoneNumbers.controls; track $index) {
<div>
<input [formControlName]="$index" placeholder="電話番号">
<button type="button" (click)="removePhoneNumber($index)">削除</button>
</div>
}
</div>
<button type="button" (click)="addPhoneNumber()">電話番号を追加</button>
</form>
`,
})
export class PhoneListFormComponent {
form = new FormGroup({
phoneNumbers: new FormArray([
new FormControl('', Validators.required),
]),
});
get phoneNumbers(): FormArray {
return this.form.get('phoneNumbers') as FormArray;
}
addPhoneNumber() {
this.phoneNumbers.push(new FormControl('', Validators.required));
}
removePhoneNumber(index: number) {
this.phoneNumbers.removeAt(index);
}
}
「電話番号を追加」ボタンを押すたびに、FormArrayの中に新しいFormControlが追加され、テンプレート側もそれに追随して入力欄が増えていきます。
カスタムバリデーターの作成
組み込みのValidatorsだけでは足りない、独自のバリデーションルールも作成できます。
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
// 「英数字が両方含まれているか」をチェックする独自バリデーター
export function alphanumericValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value as string;
if (!value) return null;
const hasLetter = /[a-zA-Z]/.test(value);
const hasNumber = /[0-9]/.test(value);
return hasLetter && hasNumber ? null : { alphanumeric: true };
};
}
password = new FormControl('', [Validators.required, Validators.minLength(8), alphanumericValidator()]);
Signal Forms(Angular 22で安定版)
Angular 22で、フォームの状態をSignalsで扱うSignal Formsが安定版になりました。
基本的な考え方はReactive Formsに近いですが、内部の状態管理がSignalベースになっている点が異なります。
import { Component } from '@angular/core';
import { form, Field } from '@angular/forms/signals'; // イメージを伝えるための概念的なAPI例
@Component({
selector: 'app-signal-form-example',
standalone: true,
imports: [Field],
template: `
<input [field]="signupForm.name" />
@if (signupForm.name().invalid()) {
<span class="error">お名前を入力してください</span>
}
`,
})
export class SignalFormExampleComponent {
signupForm = form({
name: '',
});
}
※Signal Formsは登場したばかりのAPIのため、具体的な書き方は今後変わる可能性があります。
詳細は必ずAngular公式ドキュメントの最新情報を確認してください。
Signal Formsの狙いは、フォームの状態変化を、これまでのvalueChanges(Observableベース)ではなく、アプリ全体で統一されたSignalベースの仕組みに揃えることにあります。
3方式の比較
| 観点 | Template-driven Forms | Reactive Forms | Signal Forms |
|---|---|---|---|
| フォーム構造の定義場所 | テンプレート(HTML)側が中心 | TypeScript側で明示的に定義 | TypeScript側でSignalベースに定義 |
| 向いている規模 | シンプルなフォーム | 複雑・大規模なフォーム | 今後主流になる可能性がある新方式 |
| テストのしやすさ | やや低い(DOM操作が絡みやすい) | 高い(クラス単体でテスト可能) | 高い(想定) |
| 動的な項目の増減 | 苦手 |
FormArrayで対応可能 |
今後のAPI拡充次第 |
| 状態管理の内部実装 | Observable(RxJS)ベース | Observable(RxJS)ベース | Signalベース |
実務では、簡単な検索ボックスなどはTemplate-driven Formsで手早く、会員登録フォームやアンケートフォームのような複雑なものはReactive Formsで、というように使い分けられることが多いです。
Signal Formsは2026年時点でまだ登場したばかりの機能であり、今後の主要な選択肢になっていく可能性があります。
まとめ
| 用語 | 役割 |
|---|---|
| Template-driven Forms |
[(ngModel)]を中心にHTML側でフォームを組み立てる、シンプルな方式 |
| Reactive Forms | TypeScript側でフォーム構造を明示的に定義する、堅牢な方式 |
| FormControl | 単一の入力項目を表すクラス |
| FormGroup | 複数のFormControlをまとめるクラス |
| FormArray | 可変長のFormControl群を扱うクラス |
| Signal Forms | Angular 22で安定版となった、Signalベースの新しいフォーム管理方式 |
次回は、Angularアプリを本番環境に届けるためのビルドツールとパフォーマンス最適化について解説します。
学習に使った書籍
本記事の内容整理にあたっても、以下の書籍を参考にしました。
- Angular 超入門 第2版 [ 掌田津耶乃 ]
- Angularアプリケーションプログラミング 開発の勘所と手順をしっかりマスター [ 山田祥寛 ]
- Angular Webアプリ開発スタートブック [ 大澤文孝 ]
※上記リンクはアフィリエイトリンクを含みます。