🍎

Angular で Form Validator を自作する

2024/06/02に公開

Angular の Form Validator を自作する方法の紹介です。

まずはビルドインの Form Validator を眺める

Angular には ビルドインの Form Valdator があります。文字数や email などの基本的な Validator は用意されています。

正規表現でパターンを絞ったり、compose で 複数の Validator をまとめられるものもあります。これらを使うと柔軟なカスタマイズも可能です。

ただ、さらにユースケースに特化した複雑な Validaton を行いたいケースもあるでしょう。
なのでこの記事では標準のものを組み合わせるのではなく、新しく Form Validaton を自作します。

Form Validator の仕様を理解する

いきなり自作を始める前に、まずは Form Validatior の仕様を理解しておく必要があります。

Form Validator は FormControl, FormGroup, FormArray (以下 FormXXX とします) Class のインスタンスを初期化する際に宣言します。

Form Validator として受け取ることができる型は ValidatorFn (もしくはその配列)と ValidatorAsyncFn (もしくはその配列)です。

FormXXX の Class は 基底クラスの AbstractControl を継承しています。そのため、Validatorを受け取るインターフェースは全て同じです。

ValidatorFn のインターフェースは、AbstractControl を受け取り map 型の ValidationErrors を返す同期的な実装です。

interface ValidatorFn {
  (control: AbstractControl<any, any>): ValidationErrors | null
}

AbstractControl では Form の value や Form に関する event など、 validation に必要な情報が含まれています。

AsyncValidatorFn は ValidatorFn は非同期版です。
AbstractControl型を受け取り map 型の ValidationErrors の Promise もしくは Observableで返す実装です。

interface AsyncValidatorFn {
  (control: AbstractControl<any, any>): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>
}

つまり「ValidatorFn もしくは AsyncValidatorFn を満たすインターフェースの関数を実装する」ことで Form Validator を自作できます。

Form Validator を自作するサンプル

上記の説明だけでは分かりにくいので、実際に自作してみます。
先ほどの説明の通り「ValidatorFn もしくは AsyncValidatorFn を満たすインターフェースの関数を実装する」を書いていきます。

以下の例は入力欄に yyyy-mm-dd 形式で Validaton する関数です。実際に存在しない日付は ValidationErrors を返すようにしています。

dateValidatorFn(control: AbstractControl): ValidationErrors | null {
  const value = control.value;

  if (!value) {
    return null;
  }

  const isValidDate = /^\d{4}-\d{2}-\d{2}$/.test(value);
  if (!isValidDate) {
    return { invalidDate: true };
  }

  const date = new Date(value);
  const isValid =
    !isNaN(date.getTime()) && value === date.toISOString().split('T')[0];
  return isValid ? null : { invalidDate: true };
}

インターフェースさえ守れば自由に実装できます。
あとは、他の Validator と同様に FormXXX の箇所で利用すればサービスに組み込めます。

...
this.#fb.group({
  date: this.#fb.control<string>('', {
    validators: [this.dateValidatorFn, Validators.required],
  }),
});
...

より詳細な実装を見たい場合は以下のコードを参考にしてください。

Discussion