😄

[Angular]2次元配列のフォームを作成する

2023/08/27に公開

背景

実務で、2次元配列をフォームとして扱う実装を担当した。
しかし、そのやり方の文献が少なく、実装に苦労したため、今回の記事を残すことにした。

AngularのFormArrayに関して

AngularのReactiveFormには、FormArrayというフォームの値を配列形式で扱う仕組みが提供されている。
この機能を使うことで、配列の値から動的にフォームを生成することができる。

詳しくは、以下の公式ドキュメントを見ると良い。

https://blog.angular-university.io/angular-form-array/

実際に作ったもの

今回は、2次元配列から作成した表の中にチェックボックスが存在するサンプルを実装してみた。
チェックを押して、Submitを行うと、画面下方にチェックしたrowとcolの値が出現するような仕様である。

イメージ画像

実際のコード

https://stackblitz.com/edit/stackblitz-starters-whmbn1?file=src%2Fmain.html

main.tsの解説

2次元配列フォーム作成に関する主要な部分を解説していく。

フォームの定義

以下のように、FormBuilderインスタンスのarrayメソッドを使うことで、配列を表現することができる。

form: FormGroup = this.formBuilder.group({
  row: this.formBuilder.array([]),
});

ソースコードを見るとわかるが、arrayメソッドはFormArray型を返している。

    /**
     * Constructs a new `FormArray` from the given array of configurations,
     * validators and options. Accepts a single generic argument, which is the type of each control
     * inside the array.
     *
     * @param controls An array of child controls or control configs. Each child control is given an
     *     index when it is registered.
     *
     * @param validatorOrOpts A synchronous validator function, or an array of such functions, or an
     *     `AbstractControlOptions` object that contains
     * validation functions and a validation trigger.
     *
     * @param asyncValidator A single async validator or array of async validator functions.
     */
    array<T>(controls: Array<T>, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormArray<ɵElement<T, null>>;

フォームの初期化

以下のコードで、フォームの初期化を行っている。

initForm(): void {
  this.row.forEach((rowIndex) => {
    const rowFormGroup = this.formBuilder.group({
      col: this.formBuilder.array([]),
    });
    const colFormArray = rowFormGroup.get('col') as FormArray;

    this.col.forEach((colIndex) => {
      const colFormGroup = this.formBuilder.group({
        row: rowIndex,
        col: colIndex,
        isChecked: false,
      });
      colFormArray.push(colFormGroup);
    });

    this.getFormArray().push(rowFormGroup);
  });
}

イメージとして、以下のような構造のフォームを作成している。

form: FormGroup {
  row: FormArray [
    FormGroup {
      col: FormArray [
        FormGroup {
          row: FormControl,
          col: FormControl,
          isChecked: FormControl,
        },
        ...
      ],
    },
    ...
  ],
}

HTMLの解説

フォームのコード

Angularにおいて、formGroup、formArrayNameなどのディレクティブはDOMとFormControlをリンクするために使われる。

<form [formGroup]="form">
  <table formArrayName="row">
    <tbody>
      <tr
        *ngFor="let rowControl of getRowControls(); let rowIndex = index"
        [formGroupName]="rowIndex"
      >
        <td formArrayName="col">
          <label
            *ngFor="let colControl of getColControls(rowControl); let colIndex = index"
            [formGroupName]="colIndex"
          >
            <input type="checkbox" formControlName="isChecked" />
          </label>
        </td>
      </tr>
    </tbody>
  </table>
</form>

フォームのディレクティブ

formGroup

フォーム全体のバインドを行う。

<form [formGroup]="myFormGroup">
myFormGroup = this.formBuilder.group({
  ...
});

formGroupName

子要素のFormGroupをDOMにバインドする。

<div formGroupName="user">
myFormGroup = this.formBuilder.group({
  user: this.formBuilder.group({...})
});

formArrayName

FormArrayをDOMにバインドする。

<div formArrayName="">
myFormGroup = this.formBuilder.group({
  groupList: this.formBuilder.array([...])
});

formControlName

FormControlをDOMにバインドする。

<input formControlName="userName">
myFormGroup = this.formBuilder.group({
  userName: ['', Validators.required]
});

Discussion