9
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Angular formのvalidation

Last updated at Posted at 2018-11-10

Angular Formのカスタムバリデーション

Postal Code、電話番号でたまに使用するカスタムバリデーションの作成のtips

  1. 単一のCustom Validator
  2. 複数のカスタムValidatior
  3. 複数のカスタム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を作成するイメージです。
工程としては、

  1. Custom Validatorのファイルの作成
  2. 使用する時に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
作りかたは少し面倒臭いです。

  1. Directiveの作成
  2. moduleに追加
  3. 必要な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>
9
20
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?