Angular で Form Validator を自作する
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