DEV Community

loading...

How to create a custom Angular async validator

amerigom profile image Amerigo Mancino ・2 min read

Angular provides a certain number of ways to validate your input data. With attributes like required or maxlength we can easily control what a user can and cannot insert in a form.

However, what if the validation has to happen on the backend? How do I implement a custom validator that is able to call a backend service and return the validation result?

In this article, I will guide you through the steps you need to perform in order to create your custom asynchronous validator.

The backend service

For the purpose of this example, let's imagine we have an endpoint which accepts, in the request body, a structure like the following one:

{
   "objectType": "SOME_OBJECT_TYPE",
   "value": "Some name"
}
Enter fullscreen mode Exit fullscreen mode

Where value is the value to validate and objectType contains some information needed by the sever.

To this call, the server responds with another object containing the field valid (the validation result) and a message:

{
   "valid": false,
   "message": "Must not be blank"
}
Enter fullscreen mode Exit fullscreen mode

The validator

To create a custom validator, we need to create a new directive by running:

ng generate directive nameIsValid
Enter fullscreen mode Exit fullscreen mode

Let's define the directive decorator first:

@Directive({
  selector: '[nameIsValid][ngModel],[nameIsValid][FormControl]',
    providers: [
        {
            provide: NG_ASYNC_VALIDATORS,
            useExisting: NameIsValidDirective, 
            multi: true
        }
    ]
})
Enter fullscreen mode Exit fullscreen mode

The code above registers the validator, so Angular is aware it exists and can use it during binding. This means that thereafter we can use it in an input field as in the following example:

<input
  type="text"
  id="name"
  name="name"
  nameIsValid
>
Enter fullscreen mode Exit fullscreen mode

Moving back to the directive, we can now populate the validator class:

export class NameIsValidDirective implements AsyncValidator {

  private validationServiceUrl = "my backend url";

  constructor(
    private http: HttpClient
  ) { }

}
Enter fullscreen mode Exit fullscreen mode

As you can see, the NameIsValidDirective class implements the AsyncValidator interface which requires us to define a validate function:

validate(control: AbstractControl): 
   Observable<ValidationErrors | null> {

    let fieldToValidate = {
        objectType: "NAME_VALUE",
        value: control.value
    };

    const request = this.validationServiceUrl

    const obs = this.http.post<any>(request, fieldToValidate)
        .pipe(
            map((validationResult) => {
                // null no error, object for error
                return validationResult.valid === true ? null : {
                  NameIsValidDirective : validationResult.message
                };
            })
        );
    return obs;
  }
Enter fullscreen mode Exit fullscreen mode

control.value contains the value the user inserted in the input field. With this information, we build the object to send to the backend, make a call and get the response: if the validation went good, we must return null, otherwise we must return an object. In this example, I chose to return the validation error message.

Conclusions

Despite typically there is no need to create custom validators at all, there are specific cases or architectural needs where we require to perform a validation on the backend side. When this case occurs, you are now aware of how custom validators work and you successfully learned how to implement one.

Discussion (0)

pic
Editor guide