Angular provided with the powerful tools for managing forms. At this series of posts, I am going through a few Reactive Forms Validation samples from the simplest one to more advance.
At the first part of the series, we are going to talk about single form control validation. Zero or any number of synchronous and asynchronous validators can be applied to a single control.
Let's start with the simplest possible example
FormControl with no validation and some angular component markup to show this FormControl.
export class SingleControlComponent {
readonly emailControl = new FormControl();
}
<label for="email">Email</label>
<input name="email" type="text" [formControl]="emailControl">
Running this sample we gonna see similar result at the browser
Add some synchronous validation to the FormControl.
We are going to use validators provided by the Angular framework. Validators.required
- requires to have a non-empty value. Validators.email
- checking email against the pattern which is based on the definition of a valid email address in the WHATWG HTML specification with some enhancements to incorporate more RFC rules.
export class SingleControlComponent {
readonly emailControl = new FormControl(null, [Validators.required, Validators.email]);
}
As the next step we need handle validation results at the control markup. FormControl API is helpfull here. hasError
method checks the presence of error code ('required', 'email', etc.) at the errors collection of the control.
<label for="email">Email</label>
<input name="email" type="text" [formControl]="emailControl">
<div class="errors">
<span *ngIf="emailControl.hasError('required')">Email is Required</span>
<span *ngIf="emailControl.hasError('email')">Email is mailformed</span>
</div>
After this changes our control should work like at the following record
Implement asynchronous validator.
The next step is going to be the implementation of some asynchronous validation. Let's create some async validator pretend to check email presence at the server.
function emailMustNotBeUsed(control: AbstractControl): Observable<ValidationErrors | null> {
return control.value === 'used@email.com'
? of({'email-is-used': 'Email was used'}).pipe(delay(2000))
: of(null).pipe(delay(2000));
}
We are delaying execution result to simulate a call to some remote resource. And now we are appening the async validator to the FromControl
export class SingleControlComponent {
readonly emailControl = new FormControl(null,
// Sync validators
[Validators.required, Validators.email],
// Async validator
emailMustNotBeUsed);
}
Handling vlidators result at the component markup by adding the following element
...
<span *ngIf="emailControl.hasError('email-is-used')">{{emailControl.getError('email-is-used')}}</span>
...
Note: Error message from the validator is shown at the UI by using getError
method. It can be helpfull if async validator return specific error message (received from the server for example).
Results of all our changes are gonna be like at the record belove
Validators execution order
The order of execution is guaranteed. At first, all sync validators are executed in order of declaration. As soon sync validators returned results and there are no errors, async validators are started. Async validators are started in order of declaration, but execution results order is not guaranteed. Let's take a look at the code.
export class SingleControlComponent {
readonly emailControl = new FormControl(null,
// Sync validators
[Validators.required, Validators.email],
// Async validators
[emailMustNotBeUsed, emailIsForbidden]);
}
The implementation of async validators used for this form control is present belove
function emailMustNotBeUsed(control: AbstractControl): Observable<ValidationErrors | null> {
console.log('First async validator stars executing');
const resutl$ = control.value === 'used@email.com'
? of({'email-is-used': 'Email was used'}).pipe(delay(2000))
: of(null).pipe(delay(2000));
return resutl$.pipe(finalize(() => console.log('First async validator completed')));
}
function emailIsForbidden(control: AbstractControl): Observable<ValidationErrors | null> {
console.log('Second async validator stars executing');
const resutl$ = control.value === 'forbidden@email.com'
? of({'email-is-forbidden': 'Email was forbidden'}).pipe(delay(1500))
: of(null).pipe(delay(1500));
return resutl$.pipe(finalize(() => console.log('Second async validator completed')));
}
The console output of running this sample is going to be exactly this:
First async validator stars executing
Second async validator stars executing
Second async validator completed
First async validator completed
First two lines are gonna be printed the same order no matter of internal async validators implementation. Order of the other two is dependant on async validation delay.
Conclusion
Thanks for reading. I hope it helped you in some way. All code samples are available at the Github.
There are two other articles available on the topic:
Angular forms validation. Part II. FormGroup validation.
Angular forms validation. Part III. Async Validators gotchas.
Discussion (0)