角度検証フォーム入力



Angular Validating Form Input



ユーザー入力の正確性と完全性を検証することで、全体的なデータ品質を向上させることができます。このページでは、UIからのユーザー入力を検証し、リアクティブ形式とテンプレート駆動形式の両方で有用な検証メッセージを表示する方法を示します。

前提条件



フォームの検証について読む前に、次のことを基本的に理解しておく必要があります。

フォームの検証を説明するためにここで使用されるリアクティブフォームとテンプレート駆動型フォームの完全なサンプルコードを入手してください。ライブサンプルを実行します。

テンプレート駆動型フォームでの入力の検証

テンプレート駆動型フォームに検証を追加するには、使用する場合と同じ検証属性を追加します ネイティブHTMLフォームの検証 。 Angularはディレクティブを使用して、これらの属性をフレームワークのバリデーター関数と照合します。

フォームコントロールの値が変更されるたびに、Angularは検証を実行し、検証エラーのリストを生成してINVALIDステータスにするか、nullを生成してVALIDステータスにします。

次に、エクスポートしてコントロールの状態を調べることができますngModelをローカルテンプレート変数に変換します。次の例のエクスポートNgModelをという変数に名前:

<input type='text' id='name' name='name' class='form-control' required minlength='4' appForbiddenName='bob' [(ngModel)]='hero.name' #name='ngModel'> <div *ngIf='name.invalid && (name.dirty || name.touched)' class='alert'> <div *ngIf='name.errors.required'> Name is required. div> <div *ngIf='name.errors.minlength'> Name must be at least 4 characters long. div> <div *ngIf='name.errors.forbiddenName'> Name cannot be Bob. div> div>

例で示されている次の機能に注意してください。

  • NS要素はHTML検証属性を運びます:必須およびminlength。また、カスタムバリデーターディレクティブを運びます。forbiddenName。詳細については、を参照してください。 カスタムバリデーター セクション。

  • #name = 'ngModel'はエクスポートしますNgModelをと呼ばれるローカル変数に名前。NgModelは、その基礎となるプロパティの多くを反映していますFormControlインスタンス。これをテンプレートで使用して、次のような制御状態を確認できます。有効で汚れた。コントロールプロパティの完全なリストについては、AbstractControlAPIリファレンスを参照してください。

    • NS* ngIfの要素はネストされたメッセージのセットを明らかにしますdivsが、名前が無効で、コントロールがいずれかです汚れているまたは触れた。

    • それぞれネストされています考えられる検証エラーの1つに対してカスタムメッセージを表示できます。のメッセージがあります必要、minlength、およびforbiddenName。

ユーザーがフォームを編集する前にバリデーターがエラーを表示しないようにするには、次のいずれかを確認する必要があります。汚れているまたはコントロールのタッチ状態。

  • ユーザーが監視フィールドの値を変更すると、コントロールは「ダーティ」としてマークされます。
  • ユーザーがフォームコントロール要素をぼかすと、コントロールは「タッチ済み」としてマークされます。

反応形式での入力の検証

反応形式では、信頼できる情報源はコンポーネントクラスです。テンプレートの属性を介してバリデーターを追加する代わりに、コンポーネントクラスのフォームコントロールモデルにバリデーター関数を直接追加します。 Angularは、コントロールの値が変更されるたびにこれらの関数を呼び出します。

バリデーター関数

バリデーター関数は、同期または非同期のいずれかです。

  • 同期バリデーター :コントロールインスタンスを取得し、検証エラーのセットまたは検証エラーのいずれかをすぐに返す同期関数ヌル。インスタンス化するときに、これらを2番目の引数として渡すことができます。FormControl。

  • 非同期バリデーター :コントロールインスタンスを取得し、後で一連の検証エラーを発行するPromiseまたはObservableを返す非同期関数またはヌル。インスタンス化するときに、これらを3番目の引数として渡すことができます。FormControl。

パフォーマンス上の理由から、Angularはすべての同期バリデーターが合格した場合にのみ非同期バリデーターを実行します。エラーが設定される前に、それぞれが完了する必要があります。

組み込みのバリデーター機能

あなたはすることを選ぶことができます 独自のバリデーター関数を作成する 、またはAngularの組み込みバリデーターのいくつかを使用できます。

次のようなテンプレート駆動型フォームの属性として使用できるのと同じ組み込みバリデーター必須およびminlengthは、から関数として使用できます。バリデータークラス。組み込みバリデーターの完全なリストについては、バリデーターAPIリファレンスを参照してください。

ヒーローフォームをリアクティブフォームに更新するには、同じ組み込みバリデーターのいくつかを使用できます。今回は、次の例のように関数形式で使用します。

ngOnInit(): void { this.heroForm = new FormGroup({ name: new FormControl(this.hero.name, [ Validators.required, Validators.minLength(4), forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator. ]), alterEgo: new FormControl(this.hero.alterEgo), power: new FormControl(this.hero.power, Validators.required) }); } get name() { return this.heroForm.get('name'); } get power() { return this.heroForm.get('power'); }

この例では、名前制御は、2つの組み込みバリデーターを設定します—Validators.requiredおよびValidators.minLength(4)-および1つのカスタムバリデーター、forbiddenNameValidator。 (詳細については、を参照してください カスタムバリデーター 未満。)

これらのバリデーターはすべて同期しているため、2番目の引数として渡されます。関数を配列として渡すことで、複数のバリデーターをサポートできることに注意してください。

この例では、いくつかのゲッターメソッドも追加しています。リアクティブフォームでは、いつでもフォームコントロールにアクセスできます。親グループのgetメソッドですが、テンプレートの省略形としてゲッターを定義すると便利な場合があります。

のテンプレートを見ると名前の入力も、テンプレート駆動の例とかなり似ています。

<input type='text' id='name' class='form-control' formControlName='name' required> <div *ngIf='name.invalid && (name.dirty || name.touched)' class='alert alert-danger'> <div *ngIf='name.errors.required'> Name is required. div> <div *ngIf='name.errors.minlength'> Name must be at least 4 characters long. div> <div *ngIf='name.errors.forbiddenName'> Name cannot be Bob. div> div>

このフォームは、ディレクティブをエクスポートしないという点で、テンプレート駆動型バージョンとは異なります。代わりに、コンポーネントクラスで定義された名前ゲッター。

に注意してください必要な属性はまだテンプレートに存在します。検証には必要ありませんが、アクセシビリティの目的で保持する必要があります。

カスタムバリデーターの定義

組み込みのバリデーターは、アプリケーションのユースケースと必ずしも正確に一致するとは限らないため、カスタムバリデーターを作成する必要がある場合があります。

考えます以前のforbiddenNameValidator関数 反応型の例 。その関数の定義は次のようになります。

/** A hero's name can't match the given regular expression */ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): {[key: string]: any} | null => { const forbidden = nameRe.test(control.value); return forbidden ? {forbiddenName: {value: control.value}} : null; }; }

この関数は、正規表現を使用してを検出するファクトリです。 明確な 禁止された名前であり、バリデーター関数を返します。

このサンプルでは、​​禁止されている名前は「bob」であるため、バリデーターは「bob」を含むヒーロー名を拒否します。他の場所では、「alice」または構成正規表現が一致する任意の名前を拒否する可能性があります。

NSforbiddenNameValidatorファクトリは、構成されたバリデーター関数を返します。その関数はAngularコントロールオブジェクトを受け取り、 また 制御値が有効な場合はnull また 検証エラーオブジェクト。検証エラーオブジェクトには通常、名前が検証キーであるプロパティがあります。'forbiddenName'、およびその値は、エラーメッセージに挿入できる値の任意の辞書です。{名前}。

カスタム非同期バリデーターは同期バリデーターに似ていますが、代わりに、後でnullまたは検証エラーオブジェクトを発行するPromiseまたはobservableを返す必要があります。オブザーバブルの場合、オブザーバブルは完了する必要があります。その時点で、フォームは検証のために発行された最後の値を使用します。

リアクティブフォームへのカスタムバリデーターの追加

反応型では、関数を直接に渡すことにより、カスタムバリデーターを追加します。FormControl。

this.heroForm = new FormGroup({ name: new FormControl(this.hero.name, [ Validators.required, Validators.minLength(4), forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator. ]), alterEgo: new FormControl(this.hero.alterEgo), power: new FormControl(this.hero.power, Validators.required) });

テンプレート駆動型フォームへのカスタムバリデーターの追加

テンプレート駆動型フォームでは、ディレクティブをテンプレートに追加します。ディレクティブはバリデーター関数をラップします。たとえば、対応するForbiddenValidatorDirectiveは、forbiddenNameValidator。

Angularは、ディレクティブが自身をに登録するため、検証プロセスにおけるディレクティブの役割を認識します。次の例に示すように、NG_VALIDATORSプロバイダー。NG_VALIDATORSは、バリデーターの拡張可能なコレクションを備えた事前定義されたプロバイダーです。

providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]

次に、ディレクティブクラスはバリデーターインターフェース。Angularフォームと簡単に統合できます。これが、すべてがどのように組み合わされるかを理解するのに役立つ残りのディレクティブです。

@Directive({ selector: '[appForbiddenName]', providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}] }) export class ForbiddenValidatorDirective implements Validator { @Input('appForbiddenName') forbiddenName: string; validate(control: AbstractControl): ValidationErrors | null { return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control) : null; } }

一度ForbiddenValidatorDirectiveの準備ができました。セレクターを追加できます。appForbiddenName、それをアクティブにするための任意の入力要素。例えば:

<input type='text' id='name' name='name' class='form-control' required minlength='4' appForbiddenName='bob' [(ngModel)]='hero.name' #name='ngModel'>

カスタム検証ディレクティブがでインスタンス化されていることに注意してくださいuseExistingではなくuseClass。登録されたバリデーターは このインスタンスForbiddenValidatorDirective-フォーム内のインスタンスbob 'にバインドされたforbiddenNameプロパティ。

交換する場合useExisting withuseClassの場合、新しいクラスインスタンスを登録します。forbiddenName。

ステータスCSSクラスの制御

Angularは、多くのコントロールプロパティをCSSクラスとしてフォームコントロール要素に自動的にミラーリングします。これらのクラスを使用して、フォームの状態に応じてフォーム制御要素のスタイルを設定できます。現在、次のクラスがサポートされています。

  • .ng-有効
  • .ng-無効
  • .ng-保留中
  • .ng-pristine
  • .ng-ダーティ
  • .ng-手つかず
  • .ng-touched

次の例では、ヒーローフォームは.ng-有効および.ng-各フォームコントロールの境界線の色を設定するための無効なクラス。

.ng-valid[required], .ng-valid.required { border-left: 5px solid #42A948; /* green */ } .ng-invalid:not(form) { border-left: 5px solid #a94442; /* red */ } .alert div { background-color: #fed3d3; color: #820000; padding: 1rem; margin-bottom: 1rem; } .form-group { margin-bottom: 1rem; } label { display: block; margin-bottom: .5rem; } select { width: 100%; padding: .5rem; }

クロスフィールド検証

クロスフィールドバリデーターは カスタムバリデーター フォーム内のさまざまなフィールドの値を比較し、それらを組み合わせて受け入れるか拒否します。たとえば、相互に互換性のないオプションを提供するフォームがある場合、ユーザーはAまたはBを選択できますが、両方を選択することはできません。一部のフィールド値は他の値にも依存する場合があります。ユーザーは、Aも選択されている場合にのみ、Bを選択できる場合があります。

次の相互検証の例は、次の方法を示しています。

  • 2つの兄弟コントロールの値に基づいて、リアクティブまたはテンプレートベースのフォーム入力を検証します。
  • ユーザーがフォームを操作し、検証が失敗した後、説明的なエラーメッセージを表示します。

例では、相互検証を使用して、ヒーローフォームに記入することでヒーローが本当の身元を明かさないようにしています。バリデーターは、ヒーロー名と分身が一致しないことを確認することでこれを行います。

反応型に相互検証を追加する

フォームの構造は次のとおりです。

const heroForm = new FormGroup({ 'name': new FormControl(), 'alterEgo': new FormControl(), 'power': new FormControl() });

に注意してください名前とalterEgoは兄弟コントロールです。単一のカスタムバリデーターで両方のコントロールを評価するには、共通の祖先コントロールで検証を実行する必要があります。FormGroup。クエリを実行します子コントロールのFormGroupにより、それらの値を比較できます。

バリデーターをに追加するにはFormGroupは、作成時の2番目の引数として新しいバリデーターを渡します。

const heroForm = new FormGroup({ 'name': new FormControl(), 'alterEgo': new FormControl(), 'power': new FormControl() }, { validators: identityRevealedValidator });

バリデーターコードは次のとおりです。

/** A hero's name can't match the hero's alter ego */ export const identityRevealedValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { const name = control.get('name'); const alterEgo = control.get('alterEgo'); return name && alterEgo && name.value === alterEgo.value ? { identityRevealed: true } : null; };

NSIDバリデーターはValidatorFnインターフェース。引数としてAngularコントロールオブジェクトを受け取り、フォームが有効な場合はnullを返すか、フォームが有効な場合はnullを返します。それ以外の場合はValidationErrors。

バリデーターは、を呼び出すことによって子コントロールを取得しますFormGroupのgetメソッドは、の値を比較します名前とalterEgoコントロール。

値が一致しない場合、ヒーローのIDは秘密のままであり、両方とも有効であり、バリデーターはnullを返します。それらが一致する場合、ヒーローのIDが明らかになり、バリデーターはエラーオブジェクトを返すことによってフォームを無効としてマークする必要があります。

より良いユーザーエクスペリエンスを提供するために、フォームが無効な場合、テンプレートは適切なエラーメッセージを表示します。

<div *ngIf='heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)' class='cross-validation-error-message alert alert-danger'> Name cannot match alter ego. div>

この* ngIfは、次の場合にエラーを表示しますFormGroupには、によって返される相互検証エラーがあります。identityRevealedバリデーター、ただしユーザーが終了した場合のみ フォームとの相互作用

テンプレート駆動型フォームへの相互検証の追加

テンプレート駆動型フォームの場合、バリデーター関数をラップするディレクティブを作成する必要があります。を使用して、そのディレクティブをバリデーターとして提供します NG_VALIDATORSトークン 、次の例に示すように。

@Directive({ selector: '[appIdentityRevealed]', providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }] }) export class IdentityRevealedValidatorDirective implements Validator { validate(control: AbstractControl): ValidationErrors { return identityRevealedValidator(control); } }

新しいディレクティブをHTMLテンプレートに追加する必要があります。バリデーターはフォームの最上位に登録する必要があるため、次のテンプレートはディレクティブを形状タグ。

<form #heroForm='ngForm' appIdentityRevealed>

より良いユーザーエクスペリエンスを提供するために、フォームが無効な場合に適切なエラーメッセージを表示します。

<div *ngIf='heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)' class='cross-validation-error-message alert'> Name cannot match alter ego. div>

これは、テンプレート駆動型とリアクティブ形式の両方で同じです。

非同期バリデーターの作成

非同期バリデーターは、AsyncValidatorFnおよびAsyncValidatorインターフェース。これらは同期の対応物と非常に似ていますが、次の違いがあります。

  • NSvalidate()関数は、Promiseまたはobservableを返す必要があります。
  • 返されるオブザーバブルは有限である必要があります。つまり、ある時点で完了する必要があります。無限のオブザーバブルを有限のオブザーバブルに変換するには、オブザーバブルを次のようなフィルタリング演算子にパイプします。初め、過去、取る、またはtakeUntil。

非同期検証は同期検証の後に行われ、同期検証が成功した場合にのみ実行されます。このチェックにより、より基本的な検証メソッドがすでに無効な入力を検出した場合に、フォームが潜在的にコストのかかる非同期検証プロセス(HTTPリクエストなど)を回避できます。

非同期検証が開始された後、フォームコントロールは保留状態。あなたはコントロールのを検査することができます保留中のプロパティを使用して、進行中の検証操作に関する視覚的なフィードバックを提供します。

一般的なUIパターンは、非同期検証の実行中にスピナーを表示することです。次の例は、テンプレート駆動型のフォームでこれを実現する方法を示しています。

<input [(ngModel)]='name' #model='ngModel' appSomeAsyncValidator> <app-spinner *ngIf='model.pending'>app-spinner>

カスタム非同期バリデーターの実装

次の例では、非同期バリデーターにより、ヒーローがまだ取得されていない分身を選択することが保証されます。新しいヒーローは常に参加しており、古いヒーローはサービスを離れているため、利用可能な分身のリストを事前に取得することはできません。潜在的な分身エントリを検証するには、バリデーターは非同期操作を開始して、現在参加しているすべてのヒーローの中央データベースを参照する必要があります。

次のコードは、バリデータークラスを作成します。UniqueAlterEgoValidatorは、AsyncValidatorインターフェース。

@Injectable({ providedIn: 'root' }) export class UniqueAlterEgoValidator implements AsyncValidator { constructor(private heroesService: HeroesService) {} validate( ctrl: AbstractControl ): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> { return this.heroesService.isAlterEgoTaken(ctrl.value).pipe( map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)), catchError(() => of(null)) ); } }

コンストラクターは次のインターフェイスを定義するHeroesService。

interface HeroesService { isAlterEgoTaken: (alterEgo: string) => Observable<boolean>; }

実際のアプリケーションでは、HeroesServiceは、分身が利用可能かどうかを確認するために、ヒーローデータベースにHTTPリクエストを送信する責任があります。バリデーターの観点からは、サービスの実際の実装は重要ではないため、この例では、HeroesServiceインターフェース。

検証が始まると、UniqueAlterEgoValidatorはに委任しますHeroesService現在の制御値を使用したisAlterEgoTaken()メソッド。この時点で、コントロールは次のようにマークされます。保留中であり、監視可能なチェーンがから戻るまでこの状態のままです。validate()メソッドが完了します。

NSisAlterEgoTaken()メソッドは、分身が利用可能かどうかを確認するHTTPリクエストをディスパッチし、結果として観察可能。 NSvalidate()メソッドは、応答をパイプでつなぎます。マップ演算子を使用して、検証結果に変換します。

次に、メソッドは、他のバリデーターと同様に、次の値を返します。フォームが有効な場合はnull、およびそうでない場合はValidationErrors。このバリデーターは、catchError演算子。この場合、バリデーターは検証要求を行わなかったからといって、必ずしも分身が無効であることを意味するわけではないため、検証が成功した場合のisAlterEgoTaken()エラー。エラーを別の方法で処理して、代わりにValidationErrorオブジェクト。

しばらくすると、監視可能なチェーンが完了し、非同期検証が実行されます。 NS保留中のフラグがに設定されているfalseであり、フォームの有効性が更新されます。

非同期バリデーターのパフォーマンスの最適化

デフォルトでは、すべてのバリデーターは、フォーム値が変更されるたびに実行されます。同期バリデーターでは、これは通常、アプリケーションのパフォーマンスに目立った影響を与えません。ただし、非同期バリデーターは通常、コントロールを検証するために何らかのHTTPリクエストを実行します。キーストロークごとにHTTPリクエストをディスパッチすると、バックエンドAPIに負担がかかる可能性があるため、可能であれば回避する必要があります。

フォームの有効性の更新を遅らせるには、updateOnプロパティから(デフォルト)をに変更します送信またはぼかす。

テンプレート駆動型フォームでは、テンプレートにプロパティを設定します。

<input [(ngModel)]='name' [ngModelOptions]='{updateOn: 'blur'}'>

反応型では、プロパティをFormControlインスタンス。

new FormControl('', {updateOn: 'blur'});