Angular の FormArray で項目数が動的に増える入力フォームを実現する
Angular 4 以降の ReactiveForms で検証。
例えばユーザ情報を登録するフォームがあったとして、電話番号と種別 (固定電話なのか携帯なのかなど) を複数入力できるようにしたいとする。
画面のイメージとしては、電話番号テキストボックスと、種別テキストボックスが1行に並んでいて、「追加」ボタンを押すとその行が増やせる、みたいな作りだ。
この場合、動的にフォーム入力できる項目を増やしたりすることになるが、どうやって実装したら良いのか。調べてみると FormArray という仕組みで Angular が提供してくれていた。この使い方を紹介する。
目次
- フォームの生成と動的追加
- FormArray をまるごと変更する :
FormGroup#setControl()
- FormArray の途中にデータを入れる :
FormArray#insert()
- FormArray のデータを参照する :
FormArray.value
・FormArray.controls
- FormArray 内のデータを編集する
- 以上
フォームの生成と動的追加
まずはコンポーネント TypeScript を以下のように実装する。
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray } from '@angular/forms';
/**
* ユーザ情報登録画面
*/
@Component({
selector: 'app-user-register',
templateUrl: './user-register.component.html',
styleUrls: ['./user-register.component.scss']
})
export class UserRegisterComponent implements OnInit {
/** ユーザ情報登録フォーム */
public userForm: FormGroup;
/**
* コンストラクタ
*
* @param formBuilder FormBuilder
*/
constructor(private formBuilder: FormBuilder) { }
/**
* フォームを用意する
*/
ngOnInit() {
this.userForm = this.userFormBuilder.group({
// 名前
name: [''],
// 電話番号 : 動的に追加できる
phoneNumbers: this.userFormBuilder.array([])
});
}
/**
* formArrayName を取得するために必要な Getter
*
* @return 電話番号の FormArray
*/
get phoneNumbers(): FormArray {
return this.userForm.get('phoneNumbers') as FormArray;
}
/**
* 電話番号入力フォームを1行追加する
*/
addPhoneNumber() {
// Getter を用意したいので「this.phoneNumbers」でアクセス可能
this.phoneNumbers.push(this.userFormBuilder.group({
// 電話番号
phoneNumber: '',
// 種別 : 「固定電話」「携帯」などの情報
type: ''
});
}
次に HTML はこんな感じ。
<form [formGroup]="userForm" novalidate>
<!-- 通常の ReactiveForms どおり、userForm.name をバインディングしている -->
<p>名前 : <input type="text" formControlName="name"></p>
<!-- Getter を作成したので、TypeScript 側の this.phoneNumbers (FormArray) にアクセスできる -->
<div formArrayName="phoneNumbers">
<ul>
<li *ngFor="let phoneNumber of phoneNumbers.controls; let i = index" [formGroupName]="i">
<!-- 擬似的に表現すると、this.phoneNumbers[0].phoneNumber とのバインディングができている状態 -->
電話番号 : <input type="text" formControlName="phoneNumber">
種別 : <input type="text" formControlName="type">
</li>
</ul>
</div>
<!-- 電話番号入力フォームを1行追加する -->
<p><button type="button" (click)="addPhoneNumber()">追加</button></p>
</form>
ngOnInit()
にて、userForm
をFormBuilder#group()
で生成する。userForm
配下にphoneNumbers
というプロパティを置き (userForm.phoneNumbers
)、FormBuilder#array()
でフォーム配列である宣言だけしておく。ココでphoneNumbers
の宣言をしないと、画面上で対象のフォームが存在しないタイミングができてしまい、エラーになってしまう。- HTML 側の
formArrayName
属性で対象の FormArray を参照したりするために、専用の Getter メソッドが必要になる。 - FormArray の中には、
FormBuilder#group()
で生成した FormGroup を配列で複数格納できる。addPhoneNumber()
メソッドでやっているのがそれ。
コレで、動的に追加できるフォームを作成できた。
FormArray をまるごと変更する : FormGroup#setControl()
例えば編集画面において、取得したデータを基に編集フォームを生成するような場合。既に生成してある FormArray の中身を全量入れ替えるには、FormGroup#setControl()
を使う。
// フォームに初期値として埋め込むデータ。実際は非同期通信で取得するようなテイ
const dataList = [
{ phoneNumber: '090-0000-0000', type: '携帯' },
{ phoneNumber: '03-0000-0000', type: '自宅' }
];
// データを基に FormGroup の配列を作る
const phoneNumberFormGroups = dataList.map((data) => {
return this.userFormBuilder.group(data);
});
// FormGroup の配列を取り込んで FormArray を作る
const phoneNumbersFormArray = this.userFormBuilder.array(phoneNumberFormGroups);
// 元々のフォームに適用する
this.userForm.setControl('phoneNumbers', phoneNumbersFormArray');
this.userForm
に何か代入するワケではないので注意。
FormArray の途中にデータを入れる : FormArray#insert()
さきほどは FormArray#push()
で末尾にデータを追加したが、配列の途中に挿入したい場合は、FormArray#insert()
が使える。
// 「(this.userForm.get('phoneNumbers') as FormArray)」部分は「this.phoneNumbers」と書いても良い (Getter メソッドがあるので)
// insert() の第1引数が、第2引数の FormGroup を挿入したい配列の添字。この場合、添字 2、つまり3つ目の要素として挿入できる
(this.userForm.get('phoneNumbers') as FormArray).insert(2, this.userFormBuilder.group({
phoneNumber: '090-0000-0000',
type: '携帯'
});
FormArray のデータを参照する : FormArray.value
・FormArray.controls
FormArray のデータを参照するには、FormArray.value
や FormArray.controls
が使える。FormArray.value
は単純にオブジェクトの配列になっている。FormArray.controls
の方は、FormGroup の配列になっているので、掘り下げていけば各行のデータの操作もできる。
// フォーム配列の中身を参照する
(this.userForm.get('phoneNumbers') as FormArray).value;
// フォーム配列1件目のデータを参照する
(this.userForm.get('phoneNumbers') as FormArray).controls[0].value;
// フォーム配列1件目のデータのうち phoneNumber 項目のデータを参照する
(this.userForm.get('phoneNumbers') as FormArray).controls[0].get('phoneNumber').value;
FormArray 内のデータを編集する
先程の例で get('phoneNumber')
と出てきたので分かるかもしれないが、FormArray の中身は userForm
同様、ただの FormGroup なので、setValue()
などで編集できる。
// フォーム配列1件目のデータのうち phoneNumber 項目のデータを編集する
(this.userForm.get('phoneNumbers') as FormArray).controls[0].get('phoneNumber').setValue('03-9999-9999');
以上
このように、フォーム1行を FomArray として定義し、1行の要素は FormGroup で作成していくことで、動的に行追加できるフォームを作成できた。