リアクティブフォームは、時間の経過とともに値が変化するフォーム入力の処理に、モデル駆動型のアプローチを提供します。 このガイドでは、基本的なフォームコントロールの作成と更新、複数のコントロールをグループで使用すること、フォーム値の検証、実行時にコントロールを追加または削除できる動的フォームの作成について説明します。
リアクティブフォームの概要
リアクティブフォームは、特定の時点におけるフォームの状態を管理するために、明示的で不変のアプローチを使用します。 フォーム状態に対する各変更は、新しい状態を返します。この状態は、変更間でモデルの整合性を維持します。 リアクティブフォームは、Observableストリームに基づいて構築されています。フォーム入力と値は入力値のストリームとして提供され、同期的にアクセスできます。
リアクティブフォームは、データが要求時に一貫性があり予測可能であることが保証されるため、テストの簡単な道も提供します。 ストリームのコンシューマーは誰でも、そのデータを安全に操作できます。
リアクティブフォームは、テンプレート駆動型フォームとは異なる方法で異なります。 リアクティブフォームは、データモデルへの同期アクセス、Observable演算子を使用した不変性、Observableストリームを使用した変更追跡を提供します。
テンプレート駆動型フォームでは、テンプレート内の直接アクセスを使用してデータを変更できますが、リアクティブフォームほど明示的ではありません。これは、テンプレートに埋め込まれたディレクティブと、変更を非同期的に追跡するミュータブルデータに依存するためです。 2つのパラダイムの詳細な比較については、フォームの概要を参照してください。
基本的なフォームコントロールの追加
フォームコントロールを使用するには、次の3つの手順があります。
- Generate a new component and register the reactive forms module. This module declares the reactive-form directives that you need to use reactive forms.
- Instantiate a new
FormControl
. - テンプレートに
FormControl
を登録します。
次に、コンポーネントをテンプレートに追加することで、フォームを表示できます。
次の例では、単一のフォームコントロールを追加する方法を示します。 この例では、ユーザーは入力フィールドに名前を入力し、その入力値をキャプチャし、フォームコントロール要素の現在の値を表示します。
-
Generate a new component and import the ReactiveFormsModule
Use the CLI command
ng generate component
to generate a component in your project and importReactiveFormsModule
from the@angular/forms
package and add it to your Component'simports
array.src/app/name-editor/name-editor.component.ts (excerpt)
import {Component} from '@angular/core';import {FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'], imports: [ReactiveFormsModule],})export class NameEditorComponent { name = new FormControl(''); updateName() { this.name.setValue('Nancy'); }}
-
Declare a FormControl instance
Use the constructor of
FormControl
to set its initial value, which in this case is an empty string. By creating these controls in your component class, you get immediate access to listen for, update, and validate the state of the form input.src/app/name-editor/name-editor.component.ts
import {Component} from '@angular/core';import {FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'], imports: [ReactiveFormsModule],})export class NameEditorComponent { name = new FormControl(''); updateName() { this.name.setValue('Nancy'); }}
-
テンプレートにコントロールを登録
コンポーネントクラスでコントロールを作成したら、テンプレートのフォームコントロール要素に関連付ける必要があります。
ReactiveFormsModule
にも含まれているFormControlDirective
によって提供されるformControl
バインディングを使用して、フォームコントロールをテンプレートのフォームコントロールに関連付けます。src/app/name-editor/name-editor.component.html
<label for="name">Name: </label><input id="name" type="text" [formControl]="name"><p>Value: {{ name.value }}</p><button type="button" (click)="updateName()">Update Name</button>
テンプレートバインディング構文を使用することで、フォームコントロールがテンプレートの
name
入力要素に登録されました。フォームコントロールとDOM要素は相互に通信します。ビューはモデルの変更を反映し、モデルはビューの変更を反映します。 -
コンポーネントを表示
name
プロパティに割り当てられたFormControl
は、<app-name-editor>
コンポーネントをテンプレートに追加すると表示されます。src/app/app.component.html (名前エディター)
<h1>Reactive Forms</h1><app-name-editor /><app-profile-editor />
フォームコントロール値の表示
値は、次の方法で表示できます。
valueChanges
オブザーバブルを介して、AsyncPipe
を使用してテンプレートで、またはsubscribe()
メソッドを使用してコンポーネントクラスで、フォームの値の変更をリッスンできます。value
プロパティを使用すると、現在の値のスナップショットを取得できます。
次の例では、テンプレートの補間を使用して、現在の値を表示する方法を示します。
src/app/name-editor/name-editor.component.html (コントロール値)
<label for="name">Name: </label><input id="name" type="text" [formControl]="name"><p>Value: {{ name.value }}</p><button type="button" (click)="updateName()">Update Name</button>
表示される値は、フォームコントロール要素を更新すると変更されます。
リアクティブフォームは、各インスタンスに提供されるプロパティとメソッドを介して、特定のコントロールに関する情報にアクセスできます。 基になる AbstractControl クラスのプロパティとメソッドは、フォームの状態を制御し、入力検証 を処理するときにメッセージを表示するタイミングを判断するために使用されます。
API リファレンス で、他の FormControl
プロパティとメソッドについて説明します。
フォームコントロール値の置換
リアクティブフォームには、ユーザーの操作なしに値をプログラムで更新できる、コントロールの値をプログラムで変更するためのメソッドがあります。
フォームコントロールインスタンスは、フォームコントロールの値を更新し、提供された値の構造をコントロールの構造に対して検証する setValue()
メソッドを提供します。
たとえば、バックエンドAPIまたはサービスからフォームデータを取得する場合は、setValue()
メソッドを使用してコントロールを新しい値に更新し、古い値を完全に置き換えます。
次の例では、setValue()
メソッドを使用してコントロールの値を Nancy に更新するメソッドをコンポーネントクラスに追加します。
src/app/name-editor/name-editor.component.ts (値の更新)
import {Component} from '@angular/core';import {FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'], imports: [ReactiveFormsModule],})export class NameEditorComponent { name = new FormControl(''); updateName() { this.name.setValue('Nancy'); }}
テンプレートに、名前の更新をシミュレートするボタンを追加します。 名前の更新 ボタンをクリックすると、フォームコントロール要素に入力された値は現在の値として反映されます。
src/app/name-editor/name-editor.component.html (値の更新)
<label for="name">Name: </label><input id="name" type="text" [formControl]="name"><p>Value: {{ name.value }}</p><button type="button" (click)="updateName()">Update Name</button>
フォームモデルはコントロールの真実の源であるため、ボタンをクリックすると、コンポーネントクラス内で入力の値が変更され、現在の値が上書きされます。
HELPFUL: この例では、単一のコントロールを使用しています。
フォームグループ または フォーム配列 インスタンスで setValue()
メソッドを使用する場合は、値がグループまたは配列の構造と一致する必要があります。
フォームコントロールのグループ化
フォームには、通常、いくつかの関連するコントロールが含まれています。 リアクティブフォームは、複数の関連するコントロールを単一の入力フォームにグループ化する2つの方法を提供します。
フォームグループ | 詳細 |
---|---|
フォームグループ | 一緒に管理できるコントロールの固定セットを持つフォームを定義します。このセクションでは、フォームグループの基本について説明します。また、フォームグループのネスト を使用して、より複雑なフォームを作成することもできます。 |
フォーム配列 | 実行時にコントロールを追加および削除できる動的フォームを定義します。また、フォーム配列をネストして、より複雑なフォームを作成することもできます。このオプションの詳細については、動的フォームの作成を参照してください。 |
フォームコントロールインスタンスが単一の入力フィールドを制御するのと同じように、フォームグループインスタンスは、フォームコントロールインスタンスのグループ(たとえば、フォーム)のフォーム状態を追跡します。 フォームグループインスタンスの各コントロールは、フォームグループを作成するときに名前で追跡されます。 次の例は、単一のグループで複数のフォームコントロールインスタンスを管理する方法を示します。
ProfileEditor
コンポーネントを生成し、@angular/forms
パッケージから FormGroup
と FormControl
クラスをインポートします。
ng generate component ProfileEditor
src/app/profile-editor/profile-editor.component.ts (インポート)
import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }}
このコンポーネントにフォームグループを追加するには、次の手順を実行します。
FormGroup
インスタンスを作成します。FormGroup
モデルとビューを関連付けます。- フォームデータを保存します。
-
FormGroup インスタンスの作成
コンポーネントクラスに
profileForm
という名前のプロパティを作成し、プロパティを新しいフォームグループインスタンスに設定します。フォームグループを初期化するには、コンストラクターに、名前付きキーのオブジェクトを、そのコントロールにマップして渡します。プロファイルフォームの場合、
firstName
とlastName
という名前の2つのフォームコントロールインスタンスを追加しますsrc/app/profile-editor/profile-editor.component.ts (フォームグループ)
import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }}
個々のフォームコントロールは、グループ内に収集されました。
FormGroup
インスタンスは、グループの各コントロールの値から縮小されたオブジェクトとしてモデル値を提供します。フォームグループインスタンスには、フォームコントロールインスタンスと同じプロパティ(value
やuntouched
など)とメソッド(setValue()
など)があります。 -
FormGroup モデルとビューの関連付け
フォームグループは、その各コントロールのステータスと変更を追跡するため、コントロールのいずれかが変更されると、親コントロールも新しいステータスまたは値の変更を発行します。グループのモデルは、そのメンバーから維持されます。モデルを定義したら、テンプレートを更新して、ビューにモデルを反映する必要があります。
src/app/profile-editor/profile-editor.component.html (テンプレートフォームグループ)
<form [formGroup]="profileForm"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for(alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias: </label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div></form><p>Form Value: {{ profileForm.value | json }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
Just as a form group contains a group of controls, the profileForm
FormGroup
is bound to theform
element with theFormGroup
directive, creating a communication layer between the model and the form containing the inputs. TheformControlName
input provided by theFormControlName
directive binds each individual input to the form control defined inFormGroup
. The form controls communicate with their respective elements. They also communicate changes to the form group instance, which provides the source of truth for the model value. -
フォームデータの保存
ProfileEditor
コンポーネントはユーザーからの入力を受け付けますが、実際のシナリオでは、フォーム値をキャプチャして、コンポーネント外部でさらに処理できるようにする必要があります。FormGroup
ディレクティブは、form
要素によって発行されたsubmit
イベントをリッスンし、ngSubmit
イベントを発行します。このイベントは、コールバック関数にバインドできます。onSubmit()
コールバックメソッドを使用して、form
タグにngSubmit
イベントリスナーを追加します。src/app/profile-editor/profile-editor.component.html (submit イベント)
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
ProfileEditor
コンポーネントのonSubmit()
メソッドは、profileForm
の現在の値をキャプチャします。EventEmitter
を使用してフォームをカプセル化し、コンポーネント外部にフォーム値を提供します。次の例では、console.warn
を使用して、ブラウザコンソールにメッセージをログ出力します。src/app/profile-editor/profile-editor.component.ts (submit メソッド)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
submit
イベントは、組み込みのDOMイベントを使用してform
タグによって発行されます。submit
タイプのボタンをクリックすることで、イベントをトリガーします。これにより、ユーザーは Enter キーを押して、完了したフォームを送信できます。button
要素を使用して、フォームの最下部にボタンを追加して、フォーム送信をトリガーします。src/app/profile-editor/profile-editor.component.html (submit ボタン)
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
前述のスニペットのボタンにも、
disabled
バインディングが付けられています。これにより、profileForm
が無効な場合、ボタンが無効になります。まだ検証していないため、ボタンは常に有効になっています。基本的なフォーム検証については、フォーム入力の検証 セクションを参照してください。 -
コンポーネントを表示
フォームを含む
ProfileEditor
コンポーネントを表示するには、コンポーネントテンプレートに追加します。src/app/app.component.html (プロファイルエディター)
<h1>Reactive Forms</h1><app-name-editor /><app-profile-editor />
ProfileEditor
を使用して、フォームグループインスタンス内のfirstName
とlastName
コントロールのフォームコントロールインスタンスを管理できます。ネストされたフォームグループの作成
フォームグループは、個々のフォームコントロールインスタンスと、他のフォームグループインスタンスの両方を受け入れることができます。 これにより、複雑なフォームモデルの構成を、維持しやすく、論理的にグループ化できます。
複雑なフォームを構築する場合、情報のさまざまな領域を、より小さなセクションで管理する方が簡単です。 ネストされたフォームグループインスタンスを使用すると、大きなフォームグループを、より小さく、管理しやすいグループに分割できます。
より複雑なフォームを作成するには、次の手順を実行します。
- ネストされたグループを作成します。
- テンプレートでネストされたフォームをグループ化します。
情報のいくつかのタイプは、自然と同一のグループに分類されます。 名前と住所は、そのようなネストされたグループの典型的な例であり、次の例で使用されます。
`profileForm` にネストされたグループを作成するには、フォームグループインスタンスにネストされた `address` 要素を追加します。 src/app/profile-editor/profile-editor.component.ts (ネストされたフォームグループ)
import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }}
この例では、
address group
は、現在のfirstName
とlastName
コントロールを、新しいstreet
、city
、state
、zip
コントロールと組み合わせます。フォームグループのaddress
要素は、フォームグループの全体的なprofileForm
要素の子ですが、ステータスと値の変更に関して同じルールが適用されます。ネストされたフォームグループからのステータスと値の変更は、親フォームグループに伝達され、全体的なモデルと整合性が保たれます。 -
テンプレートでネストされたフォームをグループ化
コンポーネントクラスでモデルを更新したら、テンプレートを更新して、フォームグループインスタンスとその入力要素を接続します。
ProfileEditor
テンプレートに、street
、city
、state
、zip
フィールドを含むaddress
フォームグループを追加します。src/app/profile-editor/profile-editor.component.html (テンプレートネストされたフォームグループ)
<form [formGroup]="profileForm"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for(alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias: </label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div></form><p>Form Value: {{ profileForm.value | json }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
ProfileEditor
フォームは、1つのグループとして表示されますが、モデルはさらに分解されて、論理的なグループ化領域を表します。value
プロパティとJsonPipe
を使用して、コンポーネントテンプレートにフォームグループインスタンスの値を表示します。
データモデルの一部を更新する
複数のコントロールを含むフォームグループインスタンスの値を更新する場合は、モデルの一部だけを更新する場合があります。 このセクションでは、フォームコントロールデータモデルの特定の部分を更新する方法について説明します。
モデル値を更新するには、次の2つの方法があります。
メソッド | 詳細 |
---|---|
setValue() |
個別のコントロールに新しい値を設定します。setValue() メソッドは、フォームグループの構造に厳密に従い、コントロールの値全体を置き換えます。 |
patchValue() |
フォームモデルで変更されたオブジェクトに定義されているプロパティを置き換えます。 |
setValue()
メソッドの厳密なチェックは、複雑なフォームのネストエラーを検出するのに役立ちますが、patchValue()
は、これらのエラーでサイレントに失敗します。
ProfileEditorComponent
では、次の例を使用して、ユーザーの氏名と住所を更新する updateProfile
メソッドを使用します。
src/app/profile-editor/profile-editor.component.ts (patch value)
import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }}
テンプレートにボタンを追加して、オンデマンドでユーザープロファイルを更新することで、更新をシミュレートします。
src/app/profile-editor/profile-editor.component.html (update value)
<form [formGroup]="profileForm"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for(alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias: </label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div></form><p>Form Value: {{ profileForm.value | json }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
ユーザーがボタンをクリックすると、profileForm
モデルが、firstName
と street
の新しい値で更新されます。street
は、address
プロパティ内のオブジェクトで提供されることに注意してください。
これは、patchValue()
メソッドが、モデル構造に対して更新を適用するためです。
PatchValue()
は、フォームモデルで定義されているプロパティのみを更新します。
FormBuilder サービスを使用してコントロールを生成する
複数のフォームを扱う場合は、手動でフォームコントロールインスタンスを作成すると、反復処理になる可能性があります。
FormBuilder
サービスは、コントロールを生成するための便利なメソッドを提供します。
このサービスを利用するには、次の手順を実行します。
FormBuilder
クラスをインポートします。FormBuilder
サービスを注入します。- フォームの内容を生成します。
次の例では、FormBuilder
サービスを使用して、フォームコントロールとフォームグループインスタンスを作成し、ProfileEditor
コンポーネントをリファクタリングする方法を示します。
-
FormBuilder クラスのインポート
@angular/forms
パッケージからFormBuilder
クラスをインポートします。src/app/profile-editor/profile-editor.component.ts (インポート)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }}
-
FormBuilder サービスの注入
FormBuilder
サービスは、リアクティブフォームモジュールで提供される、注入可能なプロバイダーです。inject()
関数を使ってこの依存関係を注入します。src/app/profile-editor/profile-editor.component.ts (property init)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }}
-
フォームコントロールの生成
FormBuilder
サービスには、control()
、group()
、array()
の3つのメソッドがあります。これらは、フォームコントロール、フォームグループ、フォーム配列を含む、コンポーネントクラスでインスタンスを生成するためのファクトリメソッドです。group
メソッドを使用して、profileForm
コントロールを作成します。src/app/profile-editor/profile-editor.component.ts (フォームビルダー)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }}
前の例では、モデルのプロパティを定義するために、同じオブジェクトを使用して
group()
メソッドを使用しています。各コントロール名の値は、配列に含まれ、初期値を配列の最初の項目として含みます。TIP: コントロールを初期値だけで定義できますが、コントロールに同期または非同期検証が必要な場合は、配列の2番目と3番目の項目として同期バリデーターと非同期バリデーターを追加します。フォームビルダーの使用を、手動でインスタンスを作成する方法と比較します。
src/app/profile-editor/profile-editor.component.ts (インスタンス)
import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }}
src/app/profile-editor/profile-editor.component.ts (フォームビルダー)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }}
フォーム入力の検証
フォーム検証 は、ユーザー入力が完全で正しいことを確認するために使用されます。 このセクションでは、フォームコントロールに単一のバリデーターを追加し、フォーム全体のステータスを表示する方法について説明します。 フォーム検証については、フォーム検証 ガイドで、より詳しく説明しています。
フォーム検証を追加するには、次の手順を実行します。
- フォームコンポーネントにバリデーター関数をインポートします。
- フォームのフィールドにバリデーターを追加します。
- 検証ステータスを処理するロジックを追加します。
最も一般的な検証は、フィールドを必須にすることです。
次の例では、firstName
コントロールに必須の検証を追加し、検証の結果を表示する方法を示します。
-
検証子関数のインポート
リアクティブフォームには、一般的なユースケース向けの検証子関数のセットが含まれています。これらの関数は、検証するコントロールを受け取り、検証チェックに基づいてエラーオブジェクトまたはnull値を返します。
@angular/forms
パッケージからValidators
クラスをインポートします。src/app/profile-editor/profile-editor.component.ts (インポート)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
-
フィールドを必須にする
ProfileEditor
コンポーネントで、firstName
コントロールの配列の2番目の項目として、Validators.required
静的メソッドを追加します。src/app/profile-editor/profile-editor.component.ts (必須入力の検証)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
-
フォームステータスの表示
フォームコントロールに必須フィールドを追加すると、その初期ステータスは無効になります。この無効なステータスは、親フォームグループ要素に伝達され、そのステータスが無効になります。フォームグループインスタンスの現在のステータスには、
status
プロパティを介してアクセスできます。profileForm
の現在のステータスを補間を使用して表示します。src/app/profile-editor/profile-editor.component.html (ステータスの表示)
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
Submit ボタンは無効になっています。これは、必須の
firstName
フォームコントロールのため、profileForm
が無効になっているためです。firstName
入力に値を入力すると、フォームは有効になり、Submit ボタンが有効になります。フォーム検証の詳細については、フォーム検証 ガイドを参照してください。
動的フォームの作成
FormArray
は、名前のないコントロールを任意の数管理するための FormGroup
の代替手段です。
フォームグループインスタンスと同様に、フォーム配列インスタンスにコントロールを動的に挿入および削除でき、フォーム配列インスタンスの値と検証ステータスは、その子コントロールから計算されます。
ただし、各コントロールのキーを名前で定義する必要がないため、コントロールの数があらかじめわからない場合は、このオプションが適しています。
動的フォームを定義するには、次の手順を実行します。
FormArray
クラスをインポートします。FormArray
コントロールを定義します。- ゲッターメソッドを使用して、
FormArray
コントロールにアクセスします。 - テンプレートにフォーム配列を表示します。
次の例では、ProfileEditor
で エイリアス の配列を管理する方法を示します。
-
FormArray
クラスのインポート@angular/forms
からFormArray
クラスをインポートして、タイプ情報に使用します。FormBuilder
サービスは、FormArray
インスタンスを作成する準備ができています。src/app/profile-editor/profile-editor.component.ts (インポート)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }}
-
FormArray
コントロールの定義FormArray
を、任意の数のコントロール(0から多数)で初期化できます。これらは、配列で定義します。profileForm
のフォームグループインスタンスにaliases
プロパティを追加して、フォーム配列を定義します。FormBuilder.array()
メソッドを使用して配列を定義し、FormBuilder.control()
メソッドを使用して配列を初期コントロールで設定します。src/app/profile-editor/profile-editor.component.ts (aliases フォーム配列)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
フォームグループインスタンスのエイリアスコントロールは、動的にコントロールを追加するまで、単一のコントロールで設定されます。
-
FormArray
コントロールにアクセスprofileForm.get()
メソッドを繰り返し使用して各インスタンスを取得するのではなく、ゲッターは、フォーム配列インスタンスのエイリアスにアクセスできます。フォーム配列インスタンスは、配列内の未定義数のコントロールを表します。ゲッターを介してコントロールにアクセスするのは便利で、このアプローチは、追加のコントロールに対して簡単に繰り返すことができます。ゲッター構文を使用して、
aliases
クラスプロパティを作成し、親フォームグループからエイリアスのフォーム配列コントロールを取得します。src/app/profile-editor/profile-editor.component.ts (aliases ゲッター)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
返されるコントロールは
AbstractControl
型であるため、フォーム配列インスタンスのメソッド構文にアクセスするには、明示的な型を指定する必要があります。フォーム配列にエイリアスコントロールを動的に挿入するメソッドを定義します。FormArray.push()
メソッドは、コントロールを配列の新しい項目として挿入します。src/app/profile-editor/profile-editor.component.ts (エイリアスの追加)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
テンプレートでは、各コントロールが個別の入力フィールドとして表示されます。
-
テンプレートにフォーム配列を表示
フォームモデルからエイリアスを添付するには、テンプレートに追加する必要があります。
FormGroupNameDirective
によって提供されるformGroupName
入力と同様に、formArrayName
は、フォーム配列インスタンスからの通信を、FormArrayNameDirective
を使用してテンプレートにバインドします。次のテンプレートHTMLを、
formGroupName
要素を閉じる<div>
の後に追加します。src/app/profile-editor/profile-editor.component.html (aliases フォーム配列テンプレート)
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
@for
ブロックは、エイリアスのフォーム配列インスタンスによって提供される各フォームコントロールインスタンスを反復処理します。フォーム配列要素は名前がないため、インデックスをi
変数に割り当て、formControlName
入力に渡して、各コントロールをバインドします。新しいエイリアスインスタンスが追加されるたびに、新しいフォーム配列インスタンスに、インデックスに基づいてコントロールが提供されます。これにより、ルートコントロールのステータスと値を計算する際に、各個別のコントロールを追跡できます。
-
エイリアスの追加
最初は、フォームには
Alias
フィールドが1つ含まれています。別のフィールドを追加するには、エイリアスの追加 ボタンをクリックします。また、テンプレートの下部にあるForm Value
によって表示されるフォームモデルに報告されるエイリアスの配列も検証できます。各エイリアスについて、フォームコントロールインスタンスではなく、追加のフィールドを持つ別のフォームグループインスタンスを構成できます。各項目のコントロールを定義するプロセスは同じです。
リアクティブフォームAPIの概要
次の表は、リアクティブフォームコントロールの作成と管理に使用されるベースクラスとサービスをリストしています。 構文の詳細については、フォームパッケージ のAPIリファレンスドキュメントを参照してください。
クラス
クラス | 詳細 |
---|---|
AbstractControl |
コンクリートフォームコントロールクラス FormControl 、FormGroup 、FormArray の抽象ベースクラスです。共通の動作とプロパティを提供します。 |
FormControl |
個別のフォームコントロールの値と有効性ステータスを管理します。<input> や <select> などのHTMLフォームコントロールに対応します。 |
FormGroup |
AbstractControl インスタンスのグループの値と有効性状態を管理します。グループのプロパティには、その子コントロールが含まれます。コンポーネントの最上位フォームは FormGroup です。 |
FormArray |
数値でインデックス付けされた AbstractControl インスタンスの配列の値と有効性状態を管理します。 |
FormBuilder |
コントロールインスタンスを作成するためのファクトリメソッドを提供する、注入可能なサービスです。 |
FormRecord |
各々が同じ値タイプを持つ FormControl インスタンスのコレクションの値と有効性状態を追跡します。 |
ディレクティブ
ディレクティブ | 詳細 |
---|---|
FormControlDirective |
スタンドアロンの FormControl インスタンスをフォームコントロール要素に同期します。 |
FormControlName |
既存の FormGroup インスタンスの FormControl を、名前でフォームコントロール要素に同期します。 |
FormGroupDirective |
既存の FormGroup インスタンスをDOM要素に同期します。 |
FormGroupName |
ネストされた FormGroup インスタンスをDOM要素に同期します。 |
FormArrayName |
ネストされた FormArray インスタンスをDOM要素に同期します。 |