Книга: Angular 2 Cookbook
Назад: Creating and using a custom validator
Дальше: 4. Mastering Promises

Creating and using a custom asynchronous validator with Promises

A standard validator operates under the assumption that the validity of a certain input can be calculated in a short amount of time that the application can wait to get over with before it continues further. What's more, Angular will run this validation every time the validator is invoked, which might be quite often if form validation is bound to rapid-fire events such as keypresses.

Therefore, it makes good sense that a construct exists that will allow you to smoothly handle the validation procedures that take an arbitrary amount of time to execute or procedures that might not return at all. For this, Angular offers async Validator, which is fully compatible with Promises.

Note

The code, links, and a live example related to this recipe are available at .

Getting ready

Suppose you had started with the following skeleton application:

[app/article-editor.component.ts]      import {Component} from '@angular/core';   import {FormControl} from '@angular/forms';      @Component({     selector: 'article-editor',     template: `       <h2>New York-Style Pizza Actually Saucy Cardboard</h2>       <textarea [formControl]="bodyControl"                 placeholder="Article text">       </textarea>       <p><button (click)="saveArticle()">Save</button></p>     `   })   export class ArticleEditorComponent {     articleBody:string = '';     bodyControl:FormControl = new FormControl();          saveArticle():void {       if (this.bodyControl.valid) {         alert('Valid!');       } else {         alert('Invalid!');       }     }   }   

Your objective is to configure this form in a way that it will become valid only 5 seconds after the user enters the input in order to deter simple spambots.

How to do it...

First, create your validator class, and inside it, place a static validation method. This is similar to a synchronous validation method, but it will instead return a Promise object, passing the result data to the resolve method. The FormControl object accepts the async Validator as its third argument. If you weren't using any normal Validators, you could leave it as null.

Tip

As you would combine several Validators into one using Validators.compose, async Validators can be combined using Validators.composeAsync.

Create the validator skeleton in its own file:

[app/delay.validator.ts]      import {FormControl, Validator} from '@angular/forms';      export class DelayValidator implements Validator {     static validate(c:FormControl):Promise<{[key:string]:any}> {     }   }   

Though the validator does not yet do anything, you may still add it to the component:

[app/article-editor.component.ts]      import {Component} from '@angular/core';   import {FormControl, Validators} from '@angular/forms';   import {DelayValidator} from './delay.validator';      @Component({     selector: 'article-editor',     template: `       <h2>New York-Style Pizza Actually Saucy Cardboard</h2>       <textarea [formControl]="bodyControl"                 placeholder="Article text">       </textarea>       <p><button (click)="saveArticle()">Save</button></p>     `   })   export class ArticleEditorComponent {     articleBody:string = '';     bodyControl:FormControl =        new FormControl(null, null, DelayValidator.validate);          saveArticle():void {       if (this.bodyControl.valid) {         alert('Valid!');       } else {         alert('Invalid!');       }     }   }   

The validator must return a promise, but this promise doesn't ever need to be resolved. Furthermore, you'd like to set the delay to only one time per rendering. So in this case, you can just attach the promise to the FormControl:

[app/delay.validator.ts]      import {FormControl, Validator} from '@angular/forms';      export class DelayValidator implements Validator {     static validate(c:FormControl):Promise<{[key:string]:any}> {       if (c.pristine && !c.value) {         return new Promise;       }       if (!c.delayPromise) {         c.delayPromise = new Promise((resolve) => {           setTimeout(() => {             console.log('resolve');             resolve();           }, 5000);         });       }       return c.delayPromise;     }   }   

With this addition, the form will remain invalid until 5 seconds after the first time the value of the textarea is changed.

How it works...

Asynchronous validators are handled independently via regular (synchronous) validators, but other than their internal latency differences, they ultimately behave in nearly the exact same way. The important difference is that an async Validator, apart from the valid and invalid states that it shares with a normal Validator, has a pending state. The FormControl will remain in this state until a promise is made indicating the Validator will return either resolves or rejects.

Note

A FormControl in a pending state is treated as invalid for the purpose of checking the validity of aggregating constructs, such as FormGroup or FormArray.

In the Validator you just created, checking the pristine property of FormControl is a fine way of ascertaining whether or not the form is "fresh." Before the user modifies the input, pristine is true; following any modification (even removing all of the entered text), pristine becomes false. Therefore, it is a perfect tool in this example, as it allows us to have the FormControl maintain the form state without overcomplicating the Validator.

There's more...

It's critical to note the form that this validator takes. The validation method inside the DelayValidator class is a static method and nowhere is the DelayValidator class being instantiated. The purpose of the class is merely to house the validator. Therefore, you are unable to store information inside this class, since there are no instances in which you can do so.

Tip

In this example, you might be tempted to add member data to the validator since you want to track whether the input has been modified yet. Doing so is very much an anti-pattern! The FormControl object should act as your sole source of stateful information in this scenario. A FormControl object is instantiated for each input field, and therefore it is the ideal "datastore" with which you can track what the input is doing.

Validator execution

If you were to inspect when the validator method is being called, you would find that it executes only on a keypress inside textarea. This may seem arbitrary, but the default FormControl/input assignment is to evaluate the validators of FormControl on a change event emitted from the input. FormControl objects expose a registerOnChange method, which lets you hook onto the same point that the validators will be evaluated.

See also

  • Creating and using a custom validator demonstrates how to create a custom directive that behaves as input validation
Назад: Creating and using a custom validator
Дальше: 4. Mastering Promises

thank you
Flame
cant read the code since it is all on a single line. Also this comments section is russian
Rakuneque
DATA COLLECTION AND ANALYSIS Two reviewers extracted data and assessed methodological quality independently lasix torsemide conversion Many others were in that space already