Angular の FormArray の使い方
Angular の FormArray の使い方
Angular の ReactiveForm には、FormArray という、配列形式でフォームを取り扱うための仕組みが用意されています。
FormArrayを利用することで、ユーザに動的にフォーム項目を追加させる必要がある場面などに対応できますが、
その使い方は若干ややこしさをはらんでいます。
FormArray の宣言
FromArray は new FormArray
の形式で宣言する事ができます。
引数には配列で、初期値のフォーム要素を指定できます
例によって、FormArray へのアクセサとしての getter 定義も忘れず行っておきましょう。
@Component({
// ...
})
export class AppComponent {
name = 'Angular ' + VERSION.major;
form = new FormGroup({
name: new FormControl('', Validators.required),
tel: new FormControl('', Validators.required),
addressList: new FormArray([
new FormGroup({
zip: new FormControl('', Validators.required),
address: new FormControl('', Validators.required),
})
]),
});
get addressList(): FormArray {
return this.form.get('addressList') as FormArray;
}
}
コンストラクタ引数は AbstractForm[] となっており、FormControl だけでなく、上記のように FormGroup を要素としてもたせることができます。
コンストラクタの初期値として上記のようにフォーム要素を指定する他、単純に new FormArray([])
として空の配列で宣言することも可能です。
画面への描画
FormArray には、FormGroup 同様に controls が生えているため、
これにアクセスして FormArray の配列要素の数だけ ngFor で UI を複製することが可能です。
が、 controls の型が AbstractControl[]
となっており、そのまま参照すると型エラーを起こす場面も多いため、以下のように getter を定義して型定義を行っておくと良いでしょう。
@Component({
// ...
})
export class AppComponent {
form = new FormGroup({
name: new FormControl('', Validators.required),
tel: new FormControl('', Validators.required),
addressList: new FormArray([]),
});
// ....
/**
* this.addressList.controls は AbstractControl[] なので、
* そのまま template で利用すると input で FormGroup に渡すときに 型エラーになります。
*/
get addressListControls(): FormGroup[] {
return this.addressList.controls as unknown as FormGroup[];
}
}
ngFor で Form UI のループを記載する場合には、コンポーネント分割を行っておくと良いでしょう。
formGroup の受け渡しにうっかり [FormGroup]
や [FormControl]
などの input 名を用いると、ReactiveFormsModule が提供するディレクティブ名とバッティングするため、my などの prefix をつけてわかりやすくしておくと良いでしょう。
<app-address
*ngFor="let address of addressListControls; index as i"
[myFormGroup]="address"
(remove)="removeAddress(i)"
></app-address>
子コンポーネント(app-address
)に FormGroup を渡しておくと、子コンポーネント内で入力された値はそのまま親の form
変数で取得できます。
子コンポーネントは自分で自分自身を削除することはできないため、remove イベントのみ output として定義しておくと良いでしょう。
親要素での remove イベントのハンドラは以下のような形で実装できます。
removeAddress(i) {
this.addressList.removeAt(i);
}
フォーム要素の追加
FormArray への要素追加は、push 関数で行います。
以下のような関数を定義し (click)="addAddress()"
を宣言すれば、ボタンをクリックしてフォームの要素が増えていく様を確認できるはずです。
addAddress() {
this.addressList.push(
new FormGroup({
...
})
);
}
フォームへの値セット
フォームへの値セットは通常通り、setValue
で行うことができます。
setValue は 親の FormGroup から呼ぶこともできますし、FormArrayから直接コールすることも可能です。
this.form.setValue({
name: 'KATO TOMOHARU',
tel: '050-0000-0000',
addressList: [
{ zip: '100-0000', address: '東京都千代田区 加藤コーポレーション' },
],
});
FormArray 要素への setValue で注意が必要なのは、「内部で用意されている配列の要素数より、setValue で渡された 配列の要素数が少ない場合にエラーが発生する」ということです。
patchValue を利用してこのエラーを回避することもできるようですが、例えば 3つの配列準備がある FormArray に length 1 の配列を突っ込んでも、FormArray の要素数は 3 のままで、適用した値に応じて FormArray の要素数が縮小することはありません。
多くのケースでは、以下のような関数を作成して、引数で指定した個数で FormArray の length を調整する関数を作成することになるでしょう
private resetAddressForm(count: number) {
// ループの中で this.addressListControls.length が変化するので、
// 変数に一旦保持しないと挙動がおかしくなる
let length = this.addressListControls.length;
for (let index = 0; index < length; index++) {
this.addressList.removeAt(0);
}
for (let index = 0; index < count; index++) {
this.addressList.push(
new FormGroup({
zip: new FormControl('', Validators.required),
address: new FormControl('', Validators.required),
})
);
}
}
参考
サンプルコード(だいぶなぐり書きです。)
Discussion