Angular Formのカスタムバリデーション
Postal Code、電話番号でたまに使用するカスタムバリデーションの作成のtips
- 単一のCustom Validator
- 複数のカスタムValidatior
- 複数のカスタムValidatorをDirective化
参考
環境
- angular-cli 7.0.x
0.通常のValidation
FormModuleのValidatorsをimportし、FormControlの2つめのParameterにValidationをいれる。
StackBlitz
app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
form: FormGroup;
constructor(
private _fb: FormBuilder
) {}
ngOnInit() {
this.form = this._buildForm();
}
submit(form: FormGroup) {
console.log(form);
}
get phonenumber() {
return <FormControl>this.form.get('phonenumber');
}
private _buildForm(): FormGroup {
return this._fb.group({
phonenumber: ['', [Validators.required]]
})
}
}
app.component.html
<form [formGroup]="form" (submit)="submit(form)">
<div>
<label>
phone number
<input type="text" formControlName="phonenumber">
</label>
<ng-container *ngIf="phonenumber.invalid && (phonenumber.touched || phonenumber.dirty)">
<p *ngIf="phonenumber.hasError('required')">必須項目です</p>
</ng-container>
</div>
<div>
<button type="submit">send</button>
</div>
</form>
1.単一のCustom Validation
独自のValidationを追加する。
基本的には通常のvalidation同様にFormControllerのValidatorに追加する。
StackBlitz
app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, FormBuilder, Validators, AbstractControl } from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
form: FormGroup;
constructor(
private _fb: FormBuilder
) {}
ngOnInit() {
this.form = this._buildForm();
}
submit(form: FormGroup) {
console.log(form);
}
get phonenumber() {
return <FormControl>this.form.get('phonenumber');
}
private _buildForm(): FormGroup {
return this._fb.group({
phonenumber: ['', [Validators.required, parseToNumberValidator]]// ここを追加
})
}
}
// ここから
function parseToNumberValidator(formControl: AbstractControl) {
const val = formControl.value;
return isNaN(Number(val)) ? { parseToNumberValidator: true } : null;
}
// ここまでを追加
app.component.html
<form [formGroup]="form" (submit)="submit(form)">
<div>
<label>
phone number
<input type="text" formControlName="phonenumber">
</label>
<ng-container *ngIf="phonenumber.invalid && (phonenumber.touched || phonenumber.dirty)">
<p *ngIf="phonenumber.hasError('required')">必須項目です</p>
<!--ここから-->
<p *ngIf="phonenumber.hasError('parseToNumberValidator')">半角数字のみ</p>
<!--ここまで-->
</ng-container>
</div>
<div>
<button type="submit">send</button>
</div>
</form>
2.複数のCustom Validator
angular/formsのValidatorの自前カスタマイズversionを作成するイメージです。
工程としては、
- Custom Validatorのファイルの作成
- 使用する時にimportする
って感じです。
sampleはこちら
0. 1までのdirectory構成
$ tree -L 1
{project_path}/src/app
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
1.Custom Validatorのファイルの作成
ここにValiation Logicを書き出すためのTSファイルを作成。
$ ng g app/custom-validator --no-spec
$ tree -L 1
{project_pah}/src/app
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
└── custom-validator.ts //これ
custom-validator.ts
import { AbstractControl, ValidationErrors } from '@angular/forms';
const PHONENUMBER_REGEXP = /^[0-9]{3}-[0-9]{4}-[0-9]{4}$/;
function isEmptyInputValue(val: any) {
return val == null || val.length === 0;
}
export class CustomValidators {
static parseToNumber(control: AbstractControl): ValidationErrors | null {
return isNaN(Number(control.value)) ? null : {
'parsetonumber': true
}
}
static phonenumber(control: AbstractControl): ValidationErrors | null {
if( isEmptyInputValue(control.value) ) {
return null;
}
return PHONENUMBER_REGEXP.test(control.value) ? null : { 'phonenumber': true };
}
}
app.component.html
<form [formGroup]="form" (submit)="submit(form)">
<div>
<label>
phone number
<input formControlName="phonenumber" type="text">
</label>
<ng-container *ngIf="phonenumber.invalid && (phonenumber.touched || phonenumber.dirty)">
<p>
<span *ngIf="phonenumber.hasError('required')">required</span>
<span *ngIf="phonenumber.hasError('phonenumber')">please 000-0000-0000</span>
</p>
</ng-container>
</div>
<div>
<button type="submit">submit</button>
</div>
</form>
app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
import { CustomValidators } from './custom-validators';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
form: FormGroup;
constructor(
private _fb: FormBuilder
) {}
ngOnInit() {
this.form = this.buildForm();
}
submit(form: FormGroup):void {
console.log(form)
}
get phonenumber():FormControl {
return <FormControl>this.form.get('phonenumber')
}
private buildForm(): FormGroup {
return this._fb.group({
phonenumber: ['',[Validators.required, CustomValidators.phonenumber] ]
})
}
}
3.カスタムValidatorのdirective化
個人的には不評でロジックの切り離しがあいまいなど言われるDirective化
stack blitz
作りかたは少し面倒臭いです。
- Directiveの作成
- moduleに追加
- 必要なelementのattrに入れる。
1.Directiveの作成
custom directive用のdirectiveの作成。
$ tree -L 1
{project_path}/src/app
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
$ ng g d custom-directive --no-spec
$ tree -L 1
{project_path}/src/app
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
└── custom-validator.directive.ts
2.moduleに追加
とりあえず_app.module.ts_ に作成したdirectiveを追加。
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { PhonenumberValidator } from './phonenumber.directive';
@NgModule({
imports: [ BrowserModule, FormsModule, ReactiveFormsModule ],
declarations: [ AppComponent, PhonenumberValidator ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
phonenumber.directiveを編集
phonenumber.directive.ts
import { Directive, forwardRef } from '@angular/core';
import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';
@Directive({
selector: '[phonenumberValidator]',
providers: [
{ multi: true, useExisting: forwardRef(() => PhonenumberValidator), provide: NG_VALIDATORS }
]
})
export class PhonenumberValidator implements Validator {
private _PHONENUMBER_REGEXP = /^[0-9]{3}-[0-9]{4}-[0-9]{4}$/;
validate(formController: AbstractControl) {
return Object.assign(
this._phonenumberValidator(formController)
)
}
private _isEmptyInputValue(value: any): boolean {
return value == null || value.length === 0;
}
private _phonenumberValidator(formController: AbstractControl) {
return this._PHONENUMBER_REGEXP.test(formController.value) ? {} : { phonenumber: true };
}
}
3.使うelementに追記
app.component.html
<form [formGroup]="form" (submit)="submit(form)">
<div>
<label>
phone number
<input type="text" phonenumberValidator formControlName="phonenumber">
</label>
<ng-container *ngIf="phonenumber.invalid && (phonenumber.touched || phonenumber.dirty)">
<p>
<span *ngIf="phonenumber.hasError('phonenumber')">XXX-YYYY-YYYY</span>
</p>
</ng-container>
<p>
</p>
</div>
<div>
<button type="submit">submit</button>
</div>
</form>