[Angular] FormGroupがネストしたReactive Formをうまく操りたい


Reactive Formで、
FormGroup でフォームを入れ子にした場合のFormControlの得方が毎回わからなくなるのでメモ

今回のサンプルに使うフォーム定義は以下のようにします。

this.form = new FormGroup({
  name: new FormControl('国会議事堂', Validators.required),
  age: new FormControl(84, Validators.required),
  address: new FormGroup({
    zipcode: new FormControl('100-0014'),
    prefecture: new FormControl('東京都'),
    detail1: new FormControl('千代田区永田町1-7-1'),
    detail2: new FormControl(''),
  }),
})

ネストしたFormControl、FormGroupを得たい

上記のフォームで、this.form.controlsの結果は以下のようになる

controls:
    address: FormGroup
        ...
        controls:
            detail1: FormControl
            detail2: FormControl
            prefecture: FormControl
            zipcode: FormControl
    age: FormControl
    name: FormControl

なので、controlsプロパティを噛ませてやる必要がある。
例えば、detail2を得たい場合

form.controls.address.controls.detail2

としなければならない。

controls乱発を避けるためのtips

エイリアスとなるgetterを定義

特にテンプレート側でFormControlを得たい場合が多々あるが、controlsが乱発して可読性が低下することがある。
例えば今回の場合入れ子になっているaddressというFormGroupを、コンポーネント変数として宣言するとシンプルに書ける。
これは公式ドキュメントでも推奨のテクニック: https://angular.jp/guide/reactive-forms#creating-dynamic-forms

get formAddress(): FormGroup {
  return this.form.get('address') as FormGroup
}

[formGroup]ディレクティブに適用する場合
今回だとネストがそこまで深くないが、もっと深くなった場合は可読性がだいぶ変わりそう。

before

<div [formGroup]="form.controls.address">

after

<div [formGroup]="formAddress">

formGroupディレクティブをつけた要素の配下は、formControlディレクティブではなく、formControlNameディレクティブを使用する

上記のgetterとの合わせ技で、更に見やすくなる。

before

<div [formGroup]="formAddress">
  <input type="text" [formControl]="formAddress.controls.detail1">
  <input type="text" [formControl]="formAddress.controls.detail2">

after

<div [formGroup]="formAddress">
  <input type="text" formControlName="detail1">
  <input type="text" formControlName="detail2">

フォーム値(value)を得たい

値を得たい場合はFormControlとは違って圧倒的に話は簡単で、
今回のサンプルで言えばthis.form.valueの結果が以下のようなkey-valueオブジェクトになっている。

{
    address: {
        zipcode: '100-0014',
        prefecture: '東京都',
        detail1: '千代田区永田町1-7-1',
        detail2: '',
    },
    age: 84,
    name: '国会議事堂',
}

ネストした要素も、素直に

form.value.address.detail2

のようにアクセスすれば良い。

参考

Angular 日本語ドキュメンテーション - リアクティブフォーム