Skip to content

Angular Forms Validation: Part I - Single control validation

Posted on:January 17, 2020 at 02:12 PM

Angular Forms Validation: Part I - Single control validation

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.

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: Sample control

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 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:

Alt Text

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: The Results

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.