序章

バリデーターは、フォームの値が特定の要件を満たしていることを確認するために使用されます。 これらは、Angularアプリケーションのテンプレート駆動型フォームまたはリアクティブフォームで使用できます。

requiredemailpatternminLengthのようないくつかの組み込みバリデーターがあります。 組み込みのバリデーターでは処理されない機能に対処するカスタムバリデーターを開発することも可能です。

たとえば、電話番号バリデーターは入力フィールドで構成され、値が10桁の長さでない限り有効とは見なされません。

これは、9桁の長さの無効な番号を提供している電話番号入力フィールドのスクリーンショットです。

Screenshot of an input field with an invalid phone number. The input is highlighted in a red border and there is an error message indicating the field expects phone number values to be 10 digits long.

そして、これは10桁の長さの有効な番号を提供している電話番号入力フィールドのスクリーンショットです。

Screenshot: of an input field with a valid phone number. The input is highlighted with a blue border and there is no error message.

このチュートリアルでは、Angularアプリケーションの電話番号入力フィールドのカスタムバリデーターを作成します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

このチュートリアルは、ノードv15.2.1、npm v6.14.8、@angular/core v11.0.0、および@angular/formsv11.0.0で検証されました。

プロジェクトの設定

このチュートリアルでは、@angular/cliで生成されたデフォルトのAngularプロジェクトからビルドします。

  1. npx @angular/cli new angular-custom-validation-example --style=css --routing=false --skip-tests

注:または、グローバルに@angular/cliをインストールすることもできます。

これにより、スタイルが「CSS」(「Sass」、「Less」、「Stylus」ではなく)に設定され、ルーティングがなく、テストがスキップされる新しいAngularプロジェクトが構成されます。

新しく作成されたプロジェクトディレクトリに移動します。

  1. cd angular-custom-validation-example

この時点で、新しいAngularプロジェクトが作成されます。

テンプレート駆動型フォームでのバリデーターの使用

ディレクティブは、テンプレート駆動型フォームでの検証に使用されます。 この例では、@angular/cliを使用してphone-number-validatorディレクティブを作成します。

まず、ターミナルを開き、dev依存関係としてインストールされた@angular/cliパッケージを使用して、新しいディレクティブを生成します。

  1. ./node_modules/@angular/cli/bin/ng generate directive phone-number-validator

これにより、phone-number-validator.directive.tsphone-number-validator.directive.spec.tsが作成されます。 また、PhoneNumberValidatorDirectiveapp.module.tsに追加します。

次に、コードエディタでphone-number-validator.directive.tsを開きます。 ValidatorAbstractControl、およびNG_VALIDATORSを追加します。

src / app / phone-number-validator.directive.ts
import { Directive } from '@angular/core';
import { AbstractControl, Validator, NG_VALIDATORS } from '@angular/forms';

@Directive({
  selector: '[appPhoneNumberValidator]',
  providers: [{
    provide: NG_VALIDATORS,
    useExisting: PhoneNumberValidatorDirective,
    multi: true
  }]
})
export class PhoneNumberValidatorDirective implements Validator {
  validate(control: AbstractControl) : {[key: string]: any} | null {
    if (control.value && control.value.length != 10) {
      return { 'phoneNumberInvalid': true };
    }
    return null;
  }
}

このコードは、@angular/formsValidatorを実装するディレクティブを作成します。 次の実装方法が必要になります:validate(control: AbstractControl): : {[key: string]: any} | null。 このバリデーターは、値が10文字の長さに等しくないという条件に失敗した場合、オブジェクト({ 'phoneNumberInvalid': true })を返します。 それ以外の場合、値が条件に合格すると、nullが返されます。

次に、ターミナルを開き、dev依存関係としてインストールされた@angular/cliパッケージを使用して、新しいディレクティブを生成します。

  1. ./node_modules/@angular/cli/bin/ng generate component template-driven-form-example --flat

このコマンドは、template-driven-form-example.component.tsおよびtemplate-driven-form-example.component.htmlファイルを作成します。 また、TemplateDrivenFormExampleComponentapp.module.tsに追加します。

次に、コードエディタでtemplate-driven-form-example.component.tsを開き、空の文字列の初期値を使用してphoneを追加します。

src / app / template-driven-form-example.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-template-driven-form-example',
  templateUrl: './template-driven-form-example.component.html',
  styleUrls: ['./template-driven-form-example.component.css']
})
export class TemplateDrivenFormExampleComponent {
  phone = '';
}

Angularは、検証関数の戻り値を FormControl / NgModelerrorsプロパティに追加します。 FormControl / NgModelerrorsプロパティが空でない場合、フォームは無効です。 errorsプロパティが空の場合、フォームは有効です。

テンプレート駆動型の形式でディレクティブを使用するには、template-driven-form-example.component.htmlを開き、次のコードを追加します。

src / app / template-driven-form-example.component.html
<div class="form-group">
  <label>Phone
    <input
      type="text"
      class="form-control"
      name="phone"
      [(ngModel)]="phone"
      #phoneNgModel="ngModel"
      appPhoneNumberValidator
      [class.is-invalid]="(phoneNgModel.touched || phoneNgModel.dirty) && phoneNgModel.errors?.phoneNumberInvalid"
    >
  </label>
  <span
    class="invalid-feedback"
    *ngIf="(phoneNgModel.touched || phoneNgModel.dirty) && phoneNgModel.errors?.phoneNumberInvalid"
    > 
      Phone number must be 10 digits
  </span>
</div>

このコードは、<input>要素と<span>をエラーメッセージとともに作成します。 <input>要素は、ディレクティブにngModelおよびappPhoneNumberValidatorセレクターを使用します。

<input>touchedまたはdirtyであり、検証に合格しなかった場合、2つのことが発生します。 まず、クラスis-invalid<input>に適用されます。 次に、エラーメッセージが表示された<span>が表示されます。

注:ここにあるクラスの一部(form-groupform-controlinvalid-feedback、およびis-valid)は、ブートストラップフレームワークの一部です。 これらはこのチュートリアルを完了するために必要ではありませんが、フォームに視覚的な美学を提供することができます。

次に、コードエディタでapp.module.tsを開き、FormModuleを追加します。

src / app / app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { PhoneNumberValidatorDirective } from './phone-number-validator.directive';
import { TemplateDrivenFormExampleComponent } from './template-driven-form-example.component';

@NgModule({
  declarations: [
    AppComponent
    PhoneNumberValidatorDirective,
    TemplateDrivenFormExampleComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

最後に、app.component.htmlを開き、コンテンツをTemplateDrivenFormExampleに置き換えます。

src / app / app.component.html
<app-template-driven-form-example></app-template-driven-form-example>

npm startコマンドを実行して、Webブラウザーで入力を操作できます。 電話フィールドに10文字未満または10文字を超える文字を入力すると、エラーメッセージが表示されます。

この時点で、テンプレート駆動型のディレクティブを使用するカスタムバリデーターがあります。

リアクティブフォームでのバリデーターの使用

ディレクティブの代わりに、リアクティブフォームは検証に関数を使用します。

まず、ターミナルを開き、dev依存関係としてインストールされた@angular/cliパッケージを使用して、新しいディレクティブを生成します。

  1. ./node_modules/@angular/cli/bin/ng generate component reactive-form-example --flat

このコマンドは、reactive-form-example.component.tsおよびreactive-form-example.component.htmlファイルを作成します。 また、ReactiveFormExampleComponentapp.module.tsに追加します。

次に、コードエディタでreactive-form-example.component.tsを開き、FormBuilderAbstractControlを追加します。

src / app / react-form-example.component.ts
import { Component, OnInit } from "@angular/core";
import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-reactive-form-example',
  templateUrl: './reactive-form-example.component.html',
  styleUrls: ['./reactive-form-example.component.css']
})
export class ReactiveFormExampleComponent implements OnInit {
  myForm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.myForm = this.fb.group({
      phone: ['', [ValidatePhone]]
    });
  }

  saveForm(form: FormGroup) {
    console.log('Valid?', form.valid); // true or false
    console.log('Phone Number', form.value.phone);
  }
}

function ValidatePhone(control: AbstractControl): {[key: string]: any} | null  {
  if (control.value && control.value.length != 10) {
    return { 'phoneNumberInvalid': true };
  }
  return null;
}

このコードは、ValidatePhone関数を作成し、それをFormControlのバリデーター配列に追加します。

コードエディタでreactive-form-example.component.htmlを開き、次のフォームを作成します。

src / app / react-form-example.component.html
<form
  class="needs-validation"
  novalidate
  [formGroup]="myForm"
  (ngSubmit)="saveForm(myForm)"
>
  <div class="row">
    <div class="form-group col-sm-4">
      <label>
        Phone
        <input
          type="text"
          class="form-control"
          formControlName="phone"
          [class.is-invalid]="(myForm.get('phone').touched || myForm.get('phone').dirty) && myForm.get('phone').invalid"
        >
      </label>
      <span
        class="invalid-feedback"
        *ngIf="(myForm.get('phone').touched || myForm.get('phone').dirty) && myForm.get('phone').invalid"
      >
        Phone number must be 10 digits
      </span> 
    </div>
  </div> 
</form>

テンプレート駆動型フォームとは異なり、このフォームはformを備え、[formGroup](ngSubmit)formControlName、およびgetを使用します。

次に、コードエディタでapp.module.tsを開き、ReactiveFormsModuleを追加します。

src / app / app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { PhoneNumberValidatorDirective } from './phone-number-validator.directive';
import { ReactiveFormExampleComponent } from './reactive-form-example.component';
import { TemplateDrivenFormExampleComponent } from './template-driven-form-example.component';

@NgModule({
  declarations: [
    AppComponent,
    PhoneNumberValidatorDirective,
    ReactiveFormExampleComponent,
    TemplateDrivenFormExampleComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

最後に、app.component.htmlを開き、コンテンツをReactiveFormExampleに置き換えます。

src / app / app.component.html
<app-reactive-form-example></app-reactive-form-example>

npm startコマンドを実行して、Webブラウザーで入力を操作できます。 電話フィールドに10文字未満または10文字を超える文字を入力すると、エラーメッセージが表示されます。

この時点で、リアクティブ形式の関数を使用するカスタムバリデーターがあります。

結論

この記事では、Angularアプリケーションでテンプレート駆動型フォームとリアクティブフォームのカスタム検証を追加する方法を紹介しました。

カスタム検証により、ユーザーから提供された値が期待に適合していることを確認できます。

この投稿の概念をより深く理解するには、プロバイダーに関するこの投稿にアクセスし、AbstractControlについて読んでください。