狛ログ

2022年2月22日火曜日

【Angular】Reactive formを使用して、要素が動的なフォームを作成する方法。


オフィス狛 技術部のmmm(むー)です。

先日Angularにて、Reactive Formを使用し、要素が動的に増えるフォームを作成したかったのですが、公式のページを読んでもなかなかイメージがわかず、苦労したので備忘録として残します。

今回、記事のためにAngularのバージョンは13.2.0を使用して確認しました。
業務で使用した際、Angular8系で動くことも確認しています。

対応方法

作成したかったフォームの構造は下記のようなイメージです。「ID、名前、メモ」で1セットのデータが動的に増減します。
早速ですが、コンポーネントのコードを記載します。
 // app.component.ts
import { Component, VERSION } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  constructor(private formBuilder: FormBuilder) {}

  // フォーム(枠) = formタグに該当します
  formGroup: FormGroup = this.formBuilder.group({
    features: this.formBuilder.array([]),
  });

  // 実際はAPIからの戻り値を使用すると思いますが、今回は確認用に以下の値を定義します
  list = [
    { id: 1, name: 'Haru', memo: '春' },
    { id: 2, name: 'Natsu', memo: '夏' },
  ];

  ngOnInit() {
    // 空のフォーム(中身) = formタグの中に入れる項目をまとめたものになります
    // 今回の例ですと、「ID、名前、メモ」で1つの単位となります。
    let testForm = this.formBuilder.group({});

    // 初期値とバリデーションを設定します。
    this.list.forEach((obj) => {
      testForm = this.formBuilder.group({
        name: [obj.name, Validators.required],
        memo: [obj.memo],
      });

      // フォーム(中身)をフォーム(枠)に追加します
      this.features.push(testForm);
    });
  }

  // featuresはAbstractControlを返すので、型をFormArrayにします
  get features(): FormArray {
    return this.formGroup.get('features') as FormArray;
  }
}

FormArrayを使用し、各FormControlの値をまとめることができます。
  ・FormControlは1つの入力項目(例えば、inputタグ単位)となります。
・更に、FormGroupを使用し、各FormArrayの値をまとめることができます。
  ・FormArrayが不要な場合は、FormGroupとFormControlだけで問題ありません。

HTMLは下記となります。(バリデーション等は記載していません)
 // app.component.html
<form [formGroup]="formGroup">
  <ng-container formArrayName="nestForm">
  <!-- グループ名(formGroupName)はインデックスになります -->
    <ng-container *ngFor="let val of nestForm.controls; let i = index">
      <ng-container [formGroupName]="i">
        <p>ID:{{ list[i].id }}</p>
        <p>
          名前:<input
            type="text"
            class="form-control"
            formControlName="name"
          />
        </p>
        <p>
          メモ:<input
            type="text"
            class="form-control"
            formControlName="memo"
          />
        </p>
        <hr />
      </ng-container>
    </ng-container>
  </ng-container>
  <button type="submit" [disabled]="formGroup.invalid ">更新</button>
</form>


このコードにバリデーションをチェックし、エラーメッセージを表示する処理を追加したい場合、以下のように作成することができます。
FormControlの名前を固定できるので(下記の例では、「name」)、バリデーションチェック用の処理を重複して書く必要がありません。
  // app.component.ts
// 名前が入力されているかチェックする
errorNameRequired(i: number): boolean {
    return (this.formArray.get('features')).controls[i].get('name').hasError('required');
  }

 // app.component.html
  <p>
    名前:<input
      type="text"
      class="form-control"
      formControlName="name"
    />
	 <!-- エラーメッセージを表示するためのコードを追加↓ -->
	 <ng-container *ngIf="errorNameRequired(i)">名前を入力してください。</ng-container>
  </p>


ちなみにフォームの値に直接アクセスしたい場合は、以下のように確認することができます。
console.log(this.nestForm.value[0].name) // Haru

以上となります。参考になりましたら幸いです。

参考:
https://angular.io/api/forms/FormGroup (公式)
https://angular.io/api/forms/FormArray (公式)

0 件のコメント:

コメントを投稿