I'm writing an app using Ionic 3. I need to validate a form input in a template-driven form. To do that, I need to send a HTTP request and have the server to validate it and return the result. The validation is asynchronous. How do I validate the input asynchronously? Angular provides NG_VALIDATORS
for synchronous form validation, as well asĀ NG_ASYNC_VALIDATORS
for asynchronous form validation. OK. Let's do it.
First, Let's start a new app.
ng new async-validation
To demonstrate the idea, I'll just add a form with an input in app.component.ts. It can be much more complex but the idea is still the same.
<form>
<div>
<label for="name">Name</label>
<input type="text" name="name" myExistingName [(ngModel)]="name" #nameField="ngModel">
</div>
</form>
This is a simple form with only one input. Straightforward, right? But what is myExistingName
? That is the async validator I'm going to demonstrate. It's a directive in Angular.
Here is the code:
import { Directive } from "@angular/core";
import { NG_ASYNC_VALIDATORS, AbstractControl, Validator } from "@angular/forms";
interface ValidationResult {
}
@Directive({
selector: '[myExistingName]',
providers: [{provide: NG_ASYNC_VALIDATORS, useExisting:MyExistingNameDirective, multi: true}]
})
export class MyExistingNameDirective implements Validator {
private static secretPattern: RegExp = new RegExp('^secret[a-zA-Z]*');
validate(control: AbstractControl): Promise<ValidationResult> {
const value: any = control.value;
const hasValue: boolean = (value !== undefined) && (value !== null);
let isPattern: boolean = false;
if (hasValue) {
isPattern = MyExistingNameDirective.secretPattern.test(value);
}
if (!hasValue || !isPattern) {
return Promise.resolve({
'invalidNamePattern': true,
});
} else {
return Promise.resolve(null);
}
}
}
This class implements Validator
interface. You may notice that validate
returns a Promise
. With that, you can return the result asynchronously. You can send http request and resolve that to get the result. Also pay attention to the directive decorator. It provides a NG_ASYNC_VALIDATORS
and it's using useExisting
and allows multi
. Those are what you need for the async validation. In my demonstration, I check the input value. If the value doesn't begin with "secret", it'll mark an error code invalidNamePattern
.
What do we do with the error code invalidNamePattern
? I define it and it's used in the form to display the error.
Here is the updated form.
<form>
<div>
<label for="name">Name</label>
<input type="text" name="name" myExistingName [(ngModel)]="name" #nameField="ngModel">
</div>
<div *ngIf="nameField.errors && !nameField.pristine">
<div *ngIf="nameField.errors.invalidNamePattern">
It's not a secret name.
</div>
</div>
</form>
Note that I need to check whether errors
is set. Since it's asynchronously, sometimes it's not set yet but invalid
is set. If you depends on invalid
to decide whether to show error message and then use errors
in showing error message, then you may find that errors
is not defined while invalid
is true.
With that, when I type in text that doesn't begin with "secret", it shows me an error.
That looks simple, right? When you write your own synchronous form validator, you'll find that there are only three differences for asynchronous counterpart:
- Provide
NG_ASYNC_VALIDATORS
- Return
Promise
- Use
errors
to decide whether to show the error message.
Hope you find it useful. Please let me know if you have any helpful tips.