[Reactive Forms] FormArrayで動的に追加削除できる入力フォームを実装する

# 概要

普段の業務でAngularを使っており、リアクティブフォームの動的な追加削除を実装する機会がありました。すでに多くの記事が書かれていますが、シンプルにまとまった記事が少なかったのでまとめてみようと思います。

本記事では以下の2点について、実装を考えていきたいと思います。

1. FormArrayを使って動的に追加&削除するフォームを実装する
2. 上記を子コンポーネントに切り出す

2番目についてはこちら
[[Reactive Forms] FormArrayの実装を子コンポーネントに切り出す](https://qiita.com/mashman/items/698d2c281df6f2372446)

なお、FormGroupの基本的な説明に関しては公式ドキュメントに譲ります。
[リアクティブフォーム（Angular 日本語ドキュメント）](https://angular.jp/guide/reactive-forms)

# 動作環境

Angular CLI: 8.3.2
Node: 10.16.0

# 今回作るもの

![画面収録 2020-03-15 12.57.57.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566209/d91b8b7e-c530-c8c3-ba43-d7eb5161b879.gif)




# サンプルソース

### ReactiveFormsModuleのインポート

まずは前準備として、対応するモジュールにReactiveFormsModuleをインポートしましょう。

```app.module.ts
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ReactiveFormsModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

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

結論から書くと、最終的に以下のような実装になります。

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

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

  constructor(private fb: FormBuilder) {
  }

  ngOnInit() {
    this.form = this.createForm();
  }

  createForm(): FormGroup {
    return this.fb.group({
      userName: [''],
      userSkills: this.fb.array([])
    });
  }

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

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

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

  removeUserSkill(index: number) {
    this.userSkills.removeAt(index);
  }
}
```

手順としては、まずメインのFormGroupの中で以下のようにFormArrayを定義します。
今回は初期値として空の配列を定義しています。

```app.component.ts
  createForm(): FormGroup {
    return this.fb.group({
      userName: [''],
      userSkills: this.fb.array([])
    });
  }
```

次に追加&削除で利用するメソッドを定義しています。
頭の`get`はTypeScriptのgetterの設定です。

FormArrayはFormGroupを要素に持つ配列なので、
`userSkillForm()`はFormArrayに挿入するFormGroupを定義しています。

`userSkills()`はFormArrayの配列をそのまま返しているだけなので厳密には必要ないのですが、returnの中身を見ても分かる通り、これをいちいち呼ぶのは冗長なので簡単に呼び出せるようにしておきます。

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

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

最後にフォームの追加と削除を行うメソッドを定義します。
`addUserSkill()`ではuserSkillsの末尾にFormGroupを追加します。
`removeUserSkill(index: number)`では、受け取ったindexに対応する要素を削除します。

```app.component.ts
  addUserSkill() {
    this.userSkills.push(this.userSkillForm);
  }

  removeUserSkill(index: number) {
    this.userSkills.removeAt(index);
  }
```

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

こちらもまず、最終的な実装から

```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>

<!-- Debug用の表示 -->
<h3>Debug</h3>
<ng-container *ngIf="form.value">
  <pre>{{ form.value | json }}</pre>
</ng-container>
```

ポイントとしては、

1. `formArrayName`でFormArrayを指定する
2. .controlsでFormGroupの配列を取得し、ngForで回す
3. FormArrayの中でのformGroupNameはindexになる

# まとめ

以上、まずは一つのコンポーネントの中で動的に追加、削除するフォームを実装してみました。
長くなったので、子コンポーネントにこれらを切り出す実装は次回書きたいと思います。

[[Reactive Forms] FormArrayの実装を子コンポーネントに切り出す](https://qiita.com/mashman/items/698d2c281df6f2372446)

ご参考になれば幸いです！

# 参考文献

- [リアクティブフォーム（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/)