リアクティブフォームは、時間の経過とともに値が変化するフォーム入力の処理に、モデル駆動型のアプローチを提供します。 このガイドでは、基本的なフォームコントロールの作成と更新、複数のコントロールをグループで使用すること、フォーム値の検証、実行時にコントロールを追加または削除できる動的フォームの作成について説明します。
リアクティブフォームの概要
リアクティブフォームは、特定の時点におけるフォームの状態を管理するために、明示的で不変のアプローチを使用します。 フォーム状態に対する各変更は、新しい状態を返します。この状態は、変更間でモデルの整合性を維持します。 リアクティブフォームは、Observableストリームに基づいて構築されています。フォーム入力と値は入力値のストリームとして提供され、同期的にアクセスできます。
リアクティブフォームは、データが要求時に一貫性があり予測可能であることが保証されるため、テストの簡単な道も提供します。 ストリームのコンシューマーは誰でも、そのデータを安全に操作できます。
リアクティブフォームは、テンプレート駆動型フォームとは異なる方法で異なります。 リアクティブフォームは、データモデルへの同期アクセス、Observable演算子を使用した不変性、Observableストリームを使用した変更追跡を提供します。
テンプレート駆動型フォームでは、テンプレート内の直接アクセスを使用してデータを変更できますが、リアクティブフォームほど明示的ではありません。これは、テンプレートに埋め込まれたディレクティブと、変更を非同期的に追跡するミュータブルデータに依存するためです。 2つのパラダイムの詳細な比較については、フォームの概要を参照してください。
基本的なフォームコントロールの追加
フォームコントロールを使用するには、次の3つの手順があります。
アプリケーションにリアクティブフォームモジュールを登録します。 このモジュールは、リアクティブフォームを使用するために必要なリアクティブフォームディレクティブを宣言します。
新しいコンポーネントを生成し、新しい
FormControlをインスタンス化します。テンプレートに
FormControlを登録します。
次に、コンポーネントをテンプレートに追加することで、フォームを表示できます。
次の例では、単一のフォームコントロールを追加する方法を示します。 この例では、ユーザーは入力フィールドに名前を入力し、その入力値をキャプチャし、フォームコントロール要素の現在の値を表示します。
-
ReactiveFormsModule をインポート
リアクティブフォームコントロールを使用するには、
@angular/formsパッケージからReactiveFormsModuleをインポートし、NgModuleのimports配列に追加します。src/app/app.module.ts (抜粋)
import {BrowserModule} from '@angular/platform-browser';import {NgModule} from '@angular/core';import {ReactiveFormsModule} from '@angular/forms';import {AppComponent} from './app.component';import {NameEditorComponent} from './name-editor/name-editor.component';import {ProfileEditorComponent} from './profile-editor/profile-editor.component';@NgModule({ declarations: [AppComponent, NameEditorComponent, ProfileEditorComponent], imports: [ BrowserModule, // other imports ... ReactiveFormsModule, ], providers: [], bootstrap: [AppComponent],})export class AppModule {} -
FormControl を含む新しいコンポーネントを生成
CLI コマンド
ng generate componentを使用して、プロジェクトにコントロールをホストするコンポーネントを生成します。src/app/name-editor/name-editor.component.ts
import {Component} from '@angular/core';import {FormControl} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'],})export class NameEditorComponent { name = new FormControl(''); updateName() { this.name.setValue('Nancy'); }}FormControlのコンストラクターを使用して、初期値を設定します。この場合は、空の文字列です。これらのコントロールをコンポーネントクラスで作成することで、フォーム入力の状態をリッスン、更新、検証するためのアクセス権をすぐに得られます。 -
テンプレートにコントロールを登録
コンポーネントクラスでコントロールを作成したら、テンプレートのフォームコントロール要素に関連付ける必要があります。
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-name-editor><app-profile-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} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'],})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} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})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} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})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> <div *ngFor="let address of aliases.controls; let i=index"> <!-- 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
FormGroupは、FormGroupディレクティブを使用してform要素にバインドされ、モデルと入力を含むフォーム間の通信レイヤーを作成します。FormControlNameディレクティブによって提供されるformControlName入力により、各個別の入力がFormGroupで定義されたフォームコントロールにバインドされます。フォームコントロールは、それぞれの要素と通信します。また、モデル値の真実の源を提供するフォームグループインスタンスに変更を伝達します。 -
フォームデータの保存
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> <div *ngFor="let alias of aliases.controls; let i=index"> <!-- 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} from '@angular/core';import {FormBuilder} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})export class ProfileEditorComponent { 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; } constructor(private formBuilder: FormBuilder) {} 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> <div *ngFor="let alias of aliases.controls; let i=index"> <!-- 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-name-editor><app-profile-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} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})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> <div *ngFor="let address of aliases.controls; let i=index"> <!-- 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} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})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> <div *ngFor="let address of aliases.controls; let i=index"> <!-- 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} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})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 (コンストラクター)
import {Component, inject} from '@angular/core';import {FormBuilder} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})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} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})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} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})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} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})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} from '@angular/core';import {FormBuilder} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})export class ProfileEditorComponent { 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; } constructor(private formBuilder: FormBuilder) {} 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} from '@angular/core';import {FormBuilder} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})export class ProfileEditorComponent { 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; } constructor(private formBuilder: FormBuilder) {} 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> <div *ngFor="let alias of aliases.controls; let i=index"> <!-- 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} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})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} from '@angular/core';import {FormBuilder} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})export class ProfileEditorComponent { 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; } constructor(private formBuilder: FormBuilder) {} 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} from '@angular/core';import {FormBuilder} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})export class ProfileEditorComponent { 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; } constructor(private formBuilder: FormBuilder) {} 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} from '@angular/core';import {FormBuilder} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'],})export class ProfileEditorComponent { 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; } constructor(private formBuilder: FormBuilder) {} 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> <div *ngFor="let alias of aliases.controls; let i=index"> <!-- 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>*ngForディレクティブは、エイリアスのフォーム配列インスタンスによって提供される各フォームコントロールインスタンスを反復処理します。フォーム配列要素は名前がないため、インデックスを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要素に同期します。 |