中安拓也のブログ

プログラミングについて書くブログ

【Angular】クロスフィールドバリデーションを使用した時にフォーム全体をエラーにする

はじめに

Angularでクロスフィールドバリデーション(複数項目にまたがるバリデーション )を使った時に、フォーム全体をエラーにする方法を説明します。

なお、クロスフィールドバリデーションでフォームのなかのある項目のみをエラーにする方法についてはこちらの記事をご参照ください。

環境

SPAフレームワークのAngular/TypeScriptを使用しています。

  • Angular: 8.2.14
  • Node: 12.13.1

やりたいこと

本記事では、タイトル or 本文 のどちらかの項目に文字を入力しないと保存ボタンが有効にならないバリデーションを実装します。

f:id:l08084:20200513092345p:plain
タイトルか本文に何か入力しないと保存できない

f:id:l08084:20200513092853p:plain
本文に文字を入力したので保存ボタンが有効になる

バリデーションの実装

HTMLテンプレート

まず、HTMLテンプレートの実装について説明します。HTMLでは、タイトル欄、本文欄、保存/更新ボタンを定義しています。

upsert-form.component.html

<form class="example-form" (ngSubmit)="onSubmit(upsertNgForm)" [formGroup]="createFormGroup" #upsertNgForm="ngForm">
  <!-- タイトル欄 -->
  <mat-form-field class="example-full-width">
    <input matInput class="title" placeholder="タイトル" formControlName="title" />
  </mat-form-field>
  <!-- 本文欄 -->
  <mat-form-field class="example-full-width">
    <textarea matInput cdkTextareaAutosize cdkAutosizeMinRows="10" cdkAutosizeMaxRows="30" placeholder="本文"
      formControlName="description"></textarea>
  </mat-form-field>
  <!-- 保存/更新ボタン -->
  <button type="submit" class="login-button" mat-raised-button [disabled]="!createFormGroup.valid" color="primary">
    <ng-container *ngIf="selectedMemoId; else createLabel">更新</ng-container>
    <ng-template #createLabel>保存</ng-template>
  </button>
</form>

ポイントとしては、[disabled]="!createFormGroup.valid"と記載することで、FormGroup内でバリデーションエラーが発生している時には、ボタンが非活性になるようにしているところです。

コンポーネントクラス

続いて、コンポーネントクラスの実装について説明します。コンポーネントクラスでは、フォームグループ、フォームコントロールの設定とバリデーションの設定をしています。

upsert-form.component.ts

import { Component } from '@angular/core';
import { FormGroup, FormControl, FormBuilder, NgForm } from '@angular/forms';
import { CustomValidator } from '../../validation/custom-validator';

/**
 * メモの新規作成・更新フォーム
 *
 * @export
 * @class UpsertFormComponent
 * @implements {OnInit}
 * @implements {OnChanges}
 */
@Component({
  selector: 'app-upsert-form',
  templateUrl: './upsert-form.component.html',
  styleUrls: ['./upsert-form.component.scss']
})
export class UpsertFormComponent implements OnInit, OnChanges {
  // FormGroup定義
  public createFormGroup: FormGroup;
  // タイトルフォームのコントロール定義
  public titleControl: FormControl;
  // 本文フォームのコントロール定義
  public descriptionControl: FormControl;

  constructor(private fb: FormBuilder) {
    this.createForm();
    this.folderControl = this.createFormGroup.get('folder') as FormControl;
    this.titleControl = this.createFormGroup.get('title') as FormControl;
    this.descriptionControl = this.createFormGroup.get(
      'description'
    ) as FormControl;
  }

  /**
   * フォーム設定の作成
   *
   */
  private createForm() {
    this.createFormGroup = this.fb.group(
      {
        title: ['', []],
        description: ['', []]
      },
      {
        validators: CustomValidator.titleOrDescriptionRequired
      }
    );
  }
}

フォームグループとタイトルと本文のどちらかを必須にするバリデーション を設定している部分です。

    this.createFormGroup = this.fb.group(
      {
        title: ['', []],
        description: ['', []]
      },
      {
        validators: CustomValidator.titleOrDescriptionRequired
      }
    );

validators: CustomValidator.titleOrDescriptionRequiredという風に書くことで、クロスフィールドバリデーション (項目をまたがったバリデーション )を実装することができます。

カスタムバリデーター

最後にタイトルまたは本文のどちらかを入力しないとバリデーションエラーを返すバリデーターの実装について説明します。

custom-validator.ts

import { ValidationErrors, FormGroup } from '@angular/forms';

  /**
   * 「タイトルと本文のどちらかは必須」バリデーション
   *
   * @static
   * @param {*} ac
   * @param {*} AbstractControl
   * @memberof CustomValidator
   */
  public static titleOrDescriptionRequired(
    control: FormGroup
  ): ValidationErrors | null {
    const title = control.get('title').value;
    const description = control.get('description').value;
    return !title && !description
      ? { notTitleOrDescriptionRequired: true }
      : null;
  }
}

タイトルと本文の値をそれぞれ取得して、どちらも未入力の場合は、{ notTitleOrDescriptionRequired: true }を返しています。このようにすることで、ValidationErrorsを返されたFormGouptinvalidな状態になり、保存ボタンが非活性になります。

参考サイト

https://angular.jp/guide/form-validation