Angular formのvalidation

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

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

1. 単一のCustom Validator
2. 複数のカスタムValidatior
2. 複数のカスタムValidatorをDirective化

##### 参考

- [angular/angular](https://github.com/angular/angular/blob/master/packages/forms/src/validators.ts)

##### 環境

- angular-cli 7.0.x

## 0.通常のValidation

FormModuleのValidatorsをimportし、FormControlの2つめのParameterにValidationをいれる。
[StackBlitz](https://stackblitz.com/edit/angular-qaxpvu)


```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](https://stackblitz.com/edit/angular-qaxpvu)

```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は[こちら](https://stackblitz.com/edit/extend-validation)

### 0. 1までのdirectory構成

```terminal
$ 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ファイルを作成。

```terminal
$ 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](https://stackblitz.com/edit/custom-validator-directive)
作りかたは少し面倒臭いです。

1. Directiveの作成
2. moduleに追加
3. 必要なelementのattrに入れる。

### 1.Directiveの作成

custom directive用のdirectiveの作成。

```terminal
$ 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>
```