Angular Forms Validation: Part I - Single control validation
Michael Musatov
Posted on January 17, 2020
Angular offers powerful tools for managing forms. In this series of posts, I will discuss various Reactive Forms Validation examples, ranging from the simplest to more advanced ones.
In the first part of the series, we will focus on single form control validation. You can apply zero or any number of synchronous and asynchronous validators to a single control.
Let's start from the simplest possible example
A FormControl without validation, along with the Angular component markup to display this FormControl:
export class SingleControlComponent {
readonly emailControl = new FormControl();
}
<label for="email">Email</label>
<input name="email" type="text" [formControl]="emailControl">
Running this example will yield the following results in the browser:
Adding 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 to handle validation results in the control markup. The FormControl API is helpful here. The 'hasError' method checks for the presence of an error code ('required', 'email', etc.) in the control's errors collection.
<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 making these changes, our control should work as shown in the following recording:
Implement asynchronous validator.
The next step will be implementing some asynchronous validation. Let's create an async validator that simulates checking for email presence on 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're delaying the execution result to simulate a call to a remote resource. Now, we will add the async validator to the FormControl.
export class SingleControlComponent {
readonly emailControl = new FormControl(null,
// Sync validators
[Validators.required, Validators.email],
// Async validator
emailMustNotBeUsed);
}
Handling validator results in 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).
The results of all our changes will be as shown in the recording below:
Validators execution order
The order of execution is guaranteed. First, all synchronous validators are executed in the order of declaration. Once synchronous validators return results and there are no errors, asynchronous validators are initiated. Asynchronous validators are initiated in the order of declaration, but the order of execution results 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 asynchronous validators used for this form control is presented below:
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 will be exactly as follows:
First async validator stars executing
Second async validator stars executing
Second async validator completed
First async validator completed
The first two lines will always be printed in the same order, regardless of the internal async validator implementation. The order of the other two lines depends on the async validation delay.
Conclusion
Thank you for reading. I hope this information was helpful in some way. All code samples can be found on 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.
Posted on January 17, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.