Angularで自作のバリデーション(カスタムバリデーション)の作り方を解説していきます。
- カスタムバリデーションの作り方
- カスタムバリデーションのディレクティブの作り方
前回の振り返り
前回は、Angularのバリデーションの基礎を学びました。
この記事のソースコード
https://github.com/seteen/AngularGuides/tree/入門その09
カスタムバリデーションを作ってみる
自作のバリデーションを作ってみます。
今回は name
に指定した文字列が入っていると、エラーとなる自作のバリデーションを作ってみます。
バリデーションは、 app/shared/validators
ディレクトリに置いていきます。
実際に動くコード全部を見たい方は、後述のコミットを参照してください。
import { AbstractControl, ValidatorFn } from '@angular/forms';
export function forbiddenWordValidator(word: string): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
const forbidden = control.value.includes(word);
return forbidden ? {'forbiddenWord': {value: control.value}} : null;
};
}
... 略
export class ProductEditComponent implements OnInit {
productForm = this.fb.group({
id: [''],
name: ['', forbiddenWordValidator('ぬるぽ')], // <= 変更
price: ['', Validators.min(100)],
description: [''],
});
... 略
... 略
<input id="name" type="text" formControlName="name" required maxlength="50">
<div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert">
<div *ngIf="name.errors.required">入力してください</div>
<div *ngIf="name.errors.maxlength">50文字以内で入力してください</div>
<div *ngIf="name.errors.forbiddenWord">ガッ</div> // <= 追加
</div>
... 略
解説
forbidden-word.ts について
export function forbiddenWordValidator(word: string): ValidatorFn {
word
を受け取り、 ValidatorFn
を返すメソッドを定義しています。この ValidatorFn
を返すメソッドを作ることで、バリデータを作ることができます。
ValidatorFn
の実際のコードを見ると、下記のようになっています。
export interface ValidatorFn {
(c: AbstractControl): ValidationErrors | null;
}
この interface
の中で、 (xxx: 型): 型
のように表されるのは、メソッドのインターフェースです。
つまり、 ValidatorFn
が返り値のメソッドは、 AbstractCtonrol
を引数として、 ValidationErros
か null
を返すメソッドを返すという意味になります。
また新しく ValidationErros
という型が出てきているので確認してみます。
export declare type ValidationErrors = {
[key: string]: any;
};
これは、 string
をキーとして、何かを返すという型になります。
TIPS: interface か type か
Typescript の interface と type は似ています。
違いとしては、 typeは、 implements できない。 typeは、宣言のマージ ( declaration merging )ができないがあります。
個人的には、とりあえず interface 使ってれば良いのでは、くらいで考えています。
https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types
TIPS: Angularの実装を見る
Typescriptでは、上記のように、Angularで定義されている型をチェックしたいときが結構あります。
こういうときは、 webstorm を使っていれば、 Command+クリックで簡単に定義に飛ぶことができます。
実際のコードを見ていきましょう。
return (control: AbstractControl): {[key: string]: any} | null => {
const forbidden = control.value.includes(word);
return forbidden ? {'forbiddenWord': {value: control.value}} : null;
};
control.value
で <input>
に入力された内容が取れるので、そこに word
が含まれているかを確かめ、含まれていれば、エラーのオブジェクトを返すようにしています。
product-edit.component.ts について
name: ['', forbiddenWordValidator('ぬるぽ')], // <= 変更
name
の定義に、 forbiddenWordValidator
を追加しています。
HTMLの実装
<div *ngIf="name.errors.forbiddenWord">ガッ</div> // <= 追加
先程バリデータで返すようにしたエラーオブジェクトの key を forbiddenWord
にしたので、その値が入っているときは、 ガッ
とエラーメッセージを表示するようにしています。
動作確認
エラーメッセージが出るようになっていますね。
ここまでのコミット
カスタムバリデータのディレクティブを作る
次は、バリデータのディレクティブ(HTML上の属性)を作ってみましょう。
ディレクティブを作ると、わざわざTypescript側で設定しなくても、HTMLだけでバリデーションができます。
forbidden-word.ts
にコードを追加します。
import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn } from '@angular/forms';
import { Directive, Input } from '@angular/core';
export function forbiddenWordValidator(word: string): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
const forbidden = control.value.includes(word);
return forbidden ? {'forbiddenWord': {value: control.value}} : null;
};
}
// ↓ 追加
@Directive({
selector: '[appForbiddenWord]',
providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenWordValidatorDirective, multi: true}]
})
export class ForbiddenWordValidatorDirective implements Validator {
@Input('appForbiddenWord') forbiddenWord: string;
validate(control: AbstractControl): {[key: string]: any} | null {
return this.forbiddenWord ? forbiddenWordValidator(this.forbiddenWord)(control)
: null;
}
}
HTMLに作ったディレクティブ(属性)を追加します。
<input id="name" type="text" formControlName="name" required maxlength="50" appForbiddenWord="ぬるぽ"> // <- 変更
<div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert">
<div *ngIf="name.errors.required">入力してください</div>
<div *ngIf="name.errors.maxlength">50文字以内で入力してください</div>
<div *ngIf="name.errors.forbiddenWord">ガッ</div>
</div>
作ったディレクティブを app.module.ts
に追加します。
... 略
@NgModule({
declarations: [
...,
ForbiddenWordValidatorDirective,
],
imports: [
...
})
export class AppModule { }
Typescriptのバリデータの設定を削除します。
... 略
export class ProductEditComponent implements OnInit {
productForm = this.fb.group({
id: [''],
name: [''], // <= 変更
price: ['', Validators.min(100)],
description: [''],
});
解説
forbidden-word.ts
ディレクティブを追加しています。
@Directive({
selector: '[appForbiddenWord]',
providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenWordValidatorDirective, multi: true}]
})
この部分は、ここから定義するのはディレクティブですよ、という合図のものです。
selector
は、HTMLの属性で使う文字列を指定していて、
providers
は、バリデータ用の設定をしています。
内容はバリデータを作るときのおまじない用のようなものと思って大丈夫です( useExisting
は自分で作ったクラス名を入れる必要があります)
export class ForbiddenWordValidatorDirective implements Validator {
@Input('appForbiddenWord') forbiddenWord: string;
validate(control: AbstractControl): {[key: string]: any} | null {
return this.forbiddenWord ? forbiddenWordValidator(this.forbiddenWord)(control)
: null;
}
}
クラス定義の部分は、 Validator
を implements
し、 validate
メソッドを定義しています。
validate
メソッドは引数として、このディレクティブと一緒に使われた FormControl
を与えてくれます。
そのため、ここから取った FormControl
を、先程作った forbiddenWordValidator
に渡すという処理を行っています。
product-edit.component.html について
追加したディレクティブを <input>
要素に追加しています。
app.module.ts について
ディレクティブを定義したので、 declaration
のところに追記しています。(ディレクティブは、コンポーネント同様declaration
に追加します)
product-edit.component.ts について
HTML側で追加するようにしたので、先程追加した、 name
への forbiddenWord
のバリデータの追加部分を消しています。
動作確認
先ほどと全く同じ動作をするはずです。
ここまでのコミット
まとめ
今回は、Angularでのカスタムバリデーションの作り方について学んでいきました。
簡単に作れるので、みなさんも必要になったら作ってみてください。
次回は、API通信のやり方を解説していきます。
バックエンドは、Firebaseを使っていきます。
Angular入門 未経験から1ヶ月でサービス作れるようにする その10. Firebaseを使ったAPI通信1
入門記事一覧
「Angular入門 未経験から1ヶ月でサービス作れるようにする」は、記事数が多いため、まとめ記事 を作っています。
https://qiita.com/seteen/items/43908e33e08a39612a07