[Reactive Forms] FormArrayの実装を子コンポーネントに切り出す

# 前回やったこと

前回は下記の記事で、動的に追加＆削除できるフォームを単一のコンポーネントの中で実装しました。

前回の記事
[[Reactive Forms] FormArrayで動的に追加削除できる入力フォームを実装する](https://qiita.com/mashman/items/69cfe1a406dda8bbab80)

フォームを実装する際、複数の入力欄が必要な場合がほとんどで単一のコンポーネントに詰め込むと可読性が下がってしまいます。今回はFormArrayの実装部分を子コンポーネントに切り出していきたいと思います。

# 悩んだところ

こちらが前回のテンプレートです。

```app.component.html
<h3>フォーム</h3>

<form [formGroup]="form">
  <div>名前: <input type="text" formControlName="userName"></div>

  <div>
    <p>スキル情報</p>
    <div formArrayName="userSkills">
      <div *ngFor="let skill of userSkills.controls; let i = index">
        <div [formGroupName]="i">
          <div>スキル名: <input type="text" formControlName="skillName"></div>
          <div>レベル: <input type="number" formControlName="skillLevel"></div>
          <button (click)="removeUserSkill(i)">削除</button>
        </div>
      </div>
    </div>
    <button (click)="addUserSkills()">スキルを追加</button>
  </div>
</form>
```

私はどの階層の部分から切り出してあげると良いのか？で少し悩みました。
一旦の結論としては`FormGroup`を子コンポーネントに渡して、FormArrayの部分を丸々切り出すと上手く実装できるのでは？という結論に至りました。

# サンプルソース

ここでは`SkillForm`というコンポーネントを新たに作成し、こちらに処理を切り出していきます。

### 子コンポーネントの実装

結論から書くと以下のようなコンポーネントの実装となります。

```skill-form-component.ts
import {Component, Input, OnInit} from '@angular/core';
import {FormArray, FormBuilder, FormGroup} from '@angular/forms';

@Component({
  selector: 'app-skill-form',
  templateUrl: './skill-form.component.html',
  styleUrls: ['./skill-form.component.scss']
})
export class SkillFormComponent implements OnInit {
  @Input() parentForm: FormGroup;

  constructor(private fb: FormBuilder) { }

  ngOnInit() {
  }

  get userSkillForm(): FormGroup {
    return this.fb.group({
      skillName: [''],
      skillLevel: [''],
    });
  }

  get userSkills(): FormArray {
    return this.parentForm.get('userSkills') as FormArray;
  }

  addUserSkills() {
    this.userSkills.push(this.userSkillForm);
  }

  removeUserSkill(index: number) {
    this.userSkills.removeAt(index);
  }
}
```
ほぼそのまま移植していますが、ポイントは`@Input() parentForm: FormGroup;`で親コンポーネントのFormGroupとバインディングしている点です。

### 子テンプレートの実装


```skill-form-component.html
<ng-container [formGroup]="parentForm">
  <div>
    <p>スキル情報</p>
    <div formArrayName="userSkills">
      <div *ngFor="let skill of userSkills.controls; let i = index">
        <div [formGroupName]="i">
          <div>スキル名: <input type="text" formControlName="skillName"></div>
          <div>レベル: <input type="number" formControlName="skillLevel"></div>
          <button (click)="removeUserSkill(i)">削除</button>
        </div>
      </div>
    </div>
    <button (click)="addUserSkills()">スキルを追加</button>
  </div>
</ng-container>
```

FormArrayの記述の前に、親コンポーネントと同期したFormGroupを定義してあげることで、期待通りの挙動になります。

### 親テンプレートの実装
```app.component.html
  <form [formGroup]="form">
    <div>名前: <input type="text" formControlName="userName"></div>

    <app-skill-form [parentForm]="form"></app-skill-form>
    
  </form>
```

親テンプレートでは呼び出す時に、子コンポーネントのFormGroupに自身のFormGroupを渡してあげます。

結果として以下のように、子コンポーネントでのFormの変更を親コンポーネントが取り込めていることがわかります。

<img width="694" alt="スクリーンショット 2020-03-15 14.13.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566209/482b6782-be76-ead1-8557-a04237741dff.png">

# FormGroupではなく、FormArrayをバインディングできないのか？

できるのかもしれませんが、公式にも記述が見当たらなかったため把握できていません。
そもそもFormArrayは`[FormGroup]`が定義された中で、`FormArrayName`を宣言して利用するためFormGroupがいることが前提なのかなと推測しました。

# まとめ

FormArrayに限らずですが、子コンポーネントにリアクティブフォームの中身を切り出すときには、親のFormGroupをバインディングしてあげると上手く実装できそうです。

親コンポーネントでは最終的にサブミットされるデータの扱いに集中できるため、責務を分離した実装ができます。

間違った点がありましたら、ご指摘お願いします。

# 参考文献

- [リアクティブフォーム（Angular 日本語ドキュメント）](https://angular.jp/guide/reactive-forms)
- [Angular > Reactive Formsの使い方が初心者にわかりにくすぎるので、例を交えて取り合えず実装できる様にまとめて見た](https://qiita.com/hedrall/items/b5dc7ec2cd1c635aeaa2)
- [[Angular]入力フォームを追加削除できる動的なフォームを作ってみる](https://dev.classmethod.jp/client-side/angular-dynamic-form/)