Skip to content

Creating Custom Sync Validators in Angular Reactive Forms

In our previous blog post related to Validators, we talked about AsyncValidators, how they help us to improve to our forms, and which is the diference vs SyncValidators.

Also, we create our first AsyncValidator, but what about SyncValidators? We know they aren't the same, and when you need to use one instead of the other. So, with no further introduction, we can jump into the code.

Using Angular built-in validators

Let's say we have our registration Form defined on the code below.

  registrationForm = this.fb.group({
    name: [null, [Validators.minLength(3), Validators.required]],
    username: [null, [Validators.minLength(3), Validators.required],],
  });

Note: it is the same form we use as our initial form in our previous post's example.

Here, we are using a FormBuilder to create our reactive form. Then, we add two FormControl. In each FormControl, we are adding Validators to validate the minLength, and also make required our field. This validators, minLength, required was provided to us via Angular, which also provide maxLength, pattern in case we need something similar.

To work with validators we just need to import this line to the component:

import { FormGroup, FormControl, Validators } from '@angular/forms';

Custom Validators

But, what if we need to validate, for example, a URL, or check if it is a valid Email, or add a age validator? Well, in that case, we can build our custom SyncValidator.

Let's modify a little bit of our initial form.

  registrationForm = this.fb.group({
    name: [null, [Validators.minLength(3), Validators.required]],
    username: [null, [Validators.minLength(3), Validators.required]],
    phone: [null, [this.validatePhone(), Validators.required]]
  });

Now, we can define our custom SyncValidator.

Let's say we need to validate our phone needs to have 10 numbers. Angular doesn't provide us with phone validation, so we will have to write a custom validator for this.

  validatePhone(): {[key: string]: any} | null  {
    return (control: AbstractControl): { [key: string]: boolean } | null => {
      if (control.value && control.value.length != 10) {
        return { 'phoneNumberInvalid': true };
      }
      return null;
    }
  }

When we are creating a custom validator in Angular, it is as simple as creating another function. The only thing you need to keep in mind is that it takes one input parameter of type 'AbstractControl', and it returns an object of a key-value pair if the validation fails.

The type of the first parameter is AbstractControl, because it's a base class of FormControl, FormArray, and FormGroup, and it allows to us read the value of the control passed to the custom validator.

Then we have 2 scenarios:

  1. If the validation fails, it returns an object, which contains a key-value pair. Key is the name of the error, and the value is always Boolean: true.
  2. If the validation doesn't fail, it returns null.

So, in case we want to validate that our custom sync validator is working, we need to add the code below to our template:

  <mat-form-field class="example-full-width">
    <mat-label>Phone Number</mat-label>
    <input matInput placeholder="Ex. 256787654" formControlName="phone" />
    <mat-error
        *ngIf="(phone.touched || phone.dirty) && phone.invalid"
      >
        Phone number must be 10 digits
    </mat-error>
  </mat-form-field>
  get phone(): AbstractControl {
    return this.registrationForm.get('phone');
  }

Note: We are creating a helper to get the FormControl in template.

sync-validator

Let's see StackBlitz in action.

Conclusion

In this article, we see how we can create a custom validator (or uses the built-in from Angular) for reactive-forms in an Angular application.

Custom validation allows you to ensure values provided by users fit within your expectations.

With SyncValidators and AsyncValidators, we can ensure that the forms we are creating have the right validations, and how we can help us to prevent some unintened errors.