狛ログ

2022年8月8日月曜日

【Angular】独自エラーチェック(カスタムバリデーション)を作成する。

オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

今回は、独自エラーチェック(カスタムバリデーション)を作成します。
プログラムは、以前の記事で使用した「アカウント登録機能」を使用したいと思いますので、下記の記事も参照ください。

参照1:【Angular】コンポーネントの設計(画面ごとの設計)について。
参照2:【Angular】エラーメッセージの管理について考える。

(1)登録画面の実装を確認

まずは、現在の登録画面のコンポーネントを見てみます。
コード量が多いので、全体はこちら(register.component.ts)で確認ください
今回カスタムバリデーションを追加するのは、こちらの携帯番号(mobilePhoneNumber)部分です。
  mobilePhoneNumberMaxLength = 11;
  nameMaxLength = 20;
  // (中略)
  formRegister: FormGroup = this.formBuilder.group({
    mobilePhoneNumber: ['',[Validators.required,Validators.maxLength(this.mobilePhoneNumberMaxLength)]],
    name: ['', [Validators.maxLength(this.nameMaxLength)]],
  });

しかし、これだけだと、「18011112222」のような「0」始まりではない番号はエラーになりません。
このチェックを独自で作成しようと思いますが、
その前に、一応テンプレート側の記載も見ておこうと思います。

こちらもコード量が多いので、全体はこちら(register.component.html)で確認ください
下記に、今回関係する部分だけ記載しておきます。
    <div>
      <label>携帯電話番号<span>必須</span></label>
      <input formControlName="mobilePhoneNumber" type="tel" inputmode="numeric" class="form-control" placeholder="携帯電話番号を入力"
        [ngClass]="{'alert-danger' : v.mobilePhoneNumberInvalid}" autofocus>
      <ng-container *ngIf="v.mobilePhoneNumberInvalid">
        <p *ngIf="v.mobilePhoneNumberHasErrorRequired" class="error-message">{{ message('msg_error_field_required', '携帯電話番号') }}</p>
        <p *ngIf="v.mobilePhoneNumberHasErrorMaxLength" class="error-message">{{ message('msg_error_field_max', '携帯電話番号', mobilePhoneNumberMaxLength) }}</p>
      </ng-container>
    </div>

(2)カスタムバリデーションを作成する

弊社のプロジェクトでは、「app」配下に「shared」と言うディレクトリを作り、その中にプロジェクトで共通的に使うものを配置しています。
今回のカスタムバリデーションも、共通的に使われるものなので、、以下のように作成します。
src/
└ app/
    └ shared/
        └ validator/
            └ custom-validators.ts

では、携帯電話番号のチェックを実装しようと思います。
[custom-validators.ts]
import { FormControl } from '@angular/forms';

export class CustomValidators {
  /**
   * 携帯電話番号かどうか判定
   * @param control Formのコントロール
   */
  static mobilePhoneNumberValidator(control: FormControl) {
    const dateObj = control.value;

    // 当メソッドでは、電話番号は空文字で登録することも許可する
    if (dateObj === '') {
      return null;
    }

    const regexp = new RegExp('^(0{1}\\d{10})');
    if (
      typeof dateObj === 'undefined' ||
      dateObj === null ||
      !regexp.test(dateObj)
    ) {
      return { mobilePhoneNumberFormat: true };
    }

    return null;
  }
}

当メソッドでは、電話番号は空文字で登録することも許可する」と言うコメントの部分が特殊なのですが、例えば、「任意の項目」でこのバリデーションを使いたくなった場合、そのまま使うと「未入力」でもエラーになってしまうので、未入力はエラーとしないようにしています。
どっちにしても、必須かどうかは、通常のバリデーションで行なっているので、そちらに任せる、と言う感じです。
それと、「携帯電話番号かどうか」はかなり適当に記載していますので、ご了承ください(今回は、その説明が本質では無いので)

ちょっと分かり難い(勘違いしやすい)のですが、「エラーになるパターンは『true』を返却し、エラーとしない場合『null』を返却しています」

(3)作成したカスタムバリデーションを使う

では、作成したカスタムバリデーションを実際に使ってみます。
「register.component.ts」に実装していきますが、まずは先ほどのカスタムバリデーションをimportします。
import { CustomValidators } from '@app/shared/validator/custom-validators';

importしたカスタムバリデーションは、通常のバリデーションと同じ流れで定義します。
  formRegister: FormGroup = this.formBuilder.group({
    mobilePhoneNumber: [
      '',
      [
        Validators.required, // ←通常のバリデーション
        Validators.maxLength(this.mobilePhoneNumberMaxLength), // ←通常のバリデーション
        CustomValidators.mobilePhoneNumberValidator, // ←カスタムバリデーション
      ],
    ],
    name: ['', [Validators.maxLength(this.nameMaxLength)]],
  });

とても簡単ですね、次は、別ファイルにしている「register.validator.ts」にも追記します。
こちらは、全文そのまま記載しようと思います。
[register.component.html]
import { Injectable } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Injectable()
export class RegisterValidator {
  private form: FormGroup;

  constructor() {}

  set formGroup(form: FormGroup) {
    this.form = form;
  }

  get mobilePhoneNumberInvalid() {
    return (
      this.form.controls['mobilePhoneNumber'].invalid &&
      (this.form.controls['mobilePhoneNumber'].dirty ||
        this.form.controls['mobilePhoneNumber'].touched)
    );
  }
  get mobilePhoneNumberHasErrorRequired() {
    return this.form.controls['mobilePhoneNumber'].hasError('required');
  }
  get mobilePhoneNumberHasErrorMaxLength() {
    return (
      !this.form.controls['mobilePhoneNumber'].hasError('required') &&
      this.form.controls['mobilePhoneNumber'].hasError('maxlength')
    );
  }
  get mobilePhoneNumberHasErrorFormat() {
    return (
      !this.form.controls['mobilePhoneNumber'].hasError('required') &&
      this.form.controls['mobilePhoneNumber'].hasError(
        'mobilePhoneNumberFormat',
      )
    );
  }

  get nameInvalid() {
    return (
      this.form.controls['name'].invalid &&
      (this.form.controls['name'].dirty || this.form.controls['name'].touched)
    );
  }
  get nameHasErrorMaxLength() {
    return this.form.controls['name'].hasError('maxlength');
  }
}

追記した部分は以下の通りです。
  get mobilePhoneNumberHasErrorFormat() {
    return (
      !this.form.controls['mobilePhoneNumber'].hasError('required') &&
      this.form.controls['mobilePhoneNumber'].hasError(
        'mobilePhoneNumberFormat',
      )
    );
  }
この「mobilePhoneNumberHasErrorFormat」は、テンプレート側で使うことになります。
では、そのテンプレート側も変更しようと思います。

    <div>
      <label>携帯電話番号<span>必須</span></label>
      <input formControlName="mobilePhoneNumber" type="tel" inputmode="numeric" class="form-control" placeholder="携帯電話番号を入力"
        [ngClass]="{'alert-danger' : v.mobilePhoneNumberInvalid}" autofocus>
      <ng-container *ngIf="v.mobilePhoneNumberInvalid">
        <p *ngIf="v.mobilePhoneNumberHasErrorRequired" class="error-message">{{ message('msg_error_field_required', '携帯電話番号') }}</p>
        <p *ngIf="v.mobilePhoneNumberHasErrorMaxLength" class="error-message">{{ message('msg_error_field_max', '携帯電話番号', mobilePhoneNumberMaxLength) }}</p>
        <p *ngIf="v.mobilePhoneNumberHasErrorFormat" class="error-message">{{ message('msg_error_cellphone_number') }}</p>
      </ng-container>
    </div>

下記が追加した部分です。今回、新たにエラーメッセージも追加しています。
(エラーメッセージの管理については、「【Angular】エラーメッセージの管理について考える。」を参照ください)
<p *ngIf="v.mobilePhoneNumberHasErrorFormat" class="error-message">{{ message('msg_error_cellphone_number') }}</p>

これで、独自エラーチェック(カスタムバリデーション)を実装できました。
ある程度規模の大きいプロジェクトになると、結構な数のカスタムバリデーションを作る事になるかと思いますので、参考にして頂ければ幸いです。


0 件のコメント:

コメントを投稿