きなこもち.net

.NET Framework × UiPath,Orchestrator × Azure × AWS × Angularなどの忘備録

Angular material × form control × いろいろな入力チェック方法をためしてみた

目的

Angular MaterialのFormControlを使って実現できる入力方法を学ぶ

組み込みValidationの種類

Angularでは、以下のValidatorがデフォルトでサポートされている。参考)Angular - Validators

class Validators {
  static min(min: number): ValidatorFn
  static max(max: number): ValidatorFn
  static required(control: AbstractControl): ValidationErrors | null
  static requiredTrue(control: AbstractControl): ValidationErrors | null
  static email(control: AbstractControl): ValidationErrors | null
  static minLength(minLength: number): ValidatorFn
  static maxLength(maxLength: number): ValidatorFn
  static pattern(pattern: string | RegExp): ValidatorFn
  static nullValidator(control: AbstractControl): ValidationErrors | null
  static compose(validators: ValidatorFn[]): ValidatorFn | null
  static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn | null
}

必須入力チェック

mat-form-fieldを用意する。その中に、Inputタグを配置する。
この時、inputタグには、component.tsで作成したformControlのインスタンス(ここではtextという名前)を設定する。
formControlのValidationが失敗したときにエラーメッセージを表示するためのmat-errorタグを配置し、ngIfを設定して不要な場合は非表示にできるようにする。
なお、inputタグのrequired属性は その入力項目が入力必須であることをブラウザに知らせるためのもので、*印が付くかつかないかにかかわってくる。

<mat-form-field>
  <mat-label>Enter some text</mat-label>
  <input matInput [formControl]="text" required />
  <mat-error *ngIf="text.invalid">{{getErrorMessage()}}</mat-error>
</mat-form-field>
text = new FormControl('', [
    Validators.required
  ]);

  getErrorMessage() {
    if (this.text.hasError('required')) {
      return 'You must enter a value';
    }
    return '';
  }

正規表現でのチェック

入力項目に正規表現でのチェックを追加したい場合は、pattern Validationを利用する。今回は、入力必須で使ったComonent.tsファイルの一部を変更した。HTMLファイルは変更していない。

text = new FormControl('', [
       //ここを変更
       Validators.pattern('^[0-9]{2}$')
  ]);

  getErrorMessage() {
    if (this.text.hasError('pattern')) {
      return 'Error message.';
    }
    return '';
  }

カスタム同期バリデーション

組み込みバリデーションだけでは要件が満たせない場合は、カスタムバリデーションを利用することができる。この例では、bobという文字列を禁止したいというユースケースに利用している。メソッドの中の検証ロジックをうまいこと利用すれば、いろいろなチェックを実装することができる。

forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
     //nameRe(引数)で指定された正規表現にマッチするか確認する。
      const forbidden = nameRe.test(control.value);
      if (forbidden) {
  //禁止されている正規表現(引数)にマッチするため、
       //エラーメッセージを含めたValidationErrorsインスタンスを返す。
        return { customValidatorKey: { value: control.value } };
      } else {
        //バリデーションが正常に終了したことを呼び出し元に返す。
        return null;
      }
    };
  }

   //利用する場合は、組み込みバリデーションと同じように指定する。
  text = new FormControl(
    '',[this.forbiddenNameValidator(/bob/i)])
  );

  getErrorMessage() {
    let messageBuilder = '';
    if (this.text.errors.required) {
      messageBuilder += 'You must enter a value';
    }
    console.log(this.text.errors);
     //エラーメッセージのキーとなる値を利用してエラーがあるかを判断する。
    if (this.text.errors.customValidatorKey) {
      messageBuilder += 'aaaa';
    }
    return messageBuilder;
  }

カスタム非同期バリデーション

Web APIを利用して、入力された値がDBに存在するかを確認したりするシナリオで有効なバリデーション。下記の例では、ZipcodeServiceをHttp Clientのモックとして定義している。

@Injectable({
  providedIn: 'root',
})
export class ZipcodeService {
  private validZipcodes = ['00001', '00002', '00003', '00004'];
  fakeHttp() {
    //of(...items)—引数として提供された値を同期的に提供するObservableインスタンスを返します。
    //↑の配列を、5秒待ってから呼び出し元に返す。
    return of(this.validZipcodes).pipe(delay(100));
  }
}

次に、バリデーションの中身を実装する。

export class ZipcodeValidator {
  static createValidator(zipcodeService: ZipcodeService): AsyncValidatorFn {

    return (control: AbstractControl): Observable<ValidationErrors> => {
   //モックサービスを呼び出して、入力された値が結果の配列に含まれているか確認する。
     // 今回は、含まれていない場合が正常として処理を終了する。
     //エラーの場合は、すでに存在していることを伝えるエラーメッセージを設定する。
      return zipcodeService.fakeHttp().pipe(
        map((result: string[]) => {
          console.log(result.filter((i) => i === control.value));
          return result.filter((i) => i === control.value).length === 0
            ? null
            : {
                ZipcodeValidatorError: {
                  value: `${control.value} is exist.`,
                },
              };
        })
      );
    };
  }
}

あとは、同期バリデーションと同様に、FormControlのコンストラクタに↑で作成したValidatorを設定するだけでOK.

  //constructor(formState?: any,
  //     validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null,
  //     asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
  //非同期バリエーションは3つ目の引数で渡す。
  text = new FormControl(
    '',
    [],
    ZipcodeValidator.createValidator(this.zipcodeService)
  );

  getErrorMessage() {
    let messageBuilder = '';
 //エラーメッセージも同期バリデーションと同じように取得ができる。
    if (this.text.errors.ZipcodeValidatorError) {
      messageBuilder += this.text.errors.ZipcodeValidatorError.value;
    }
    return messageBuilder;
  }

(番外編)Composeについて

結論としては、使わなくてよい。以下、 Validators.composeを利用する理由とは?であった回答のまとめ。
- 複数のValidatorの組み合わせを使いまわすことができるため。※Composeを使わなくても同じことができる。
- compose自体、一度削除することが検討されたが、下位互換のために残された。

その他参考

Angular Reactive Forms
angular-custom-async-validators