Tutorial

Custom Async Validators in Angular

Published on July 10, 2017
Default avatar

By Alligator.io

Custom Async Validators in Angular

This tutorial is out of date and no longer maintained.

Warning: This tutorial was originally written with an earlier versions of Angular and RxJS. This code no longer works as-is.

Introduction

Angular’s @angular/forms package supports custom validators for reactive forms. However, there are certain situations where you will want a validator that validates a value with a backend API. For these scenarios, Angular provides a way to define custom async validators.

In this tutorial, you will create an Angular application that uses a custom async validator to check if an email provided by a user does not already exist in the list of users.

Setting Up the Project

This project assumes that you are building from an existing Angular project generated with Angular CLI.

In a real-world scenario, you would call a real backend. For the purposes of this tutorial, you will simulate a backend response as a JSON file containing an array of users and email addresses.

In your code editor, create a new users.json file and save it to the assets directory. Add the following example users:

assets/users.json
[
  { "name": "Paul", "email": "paul@example.com" },
  { "name": "Ringo", "email": "ringo@example.com" },
  { "name": "John", "email": "john@example.com" },
  { "name": "George", "email": "george@example.com" }
]

At this point, you have a newly created Angular application and an array of users and email addresses.

Signup Service

Next, let’s create a service that has a checkEmailNotTaken method that triggers an http GET call to our JSON file. Here we’re using RxJS’s delay operator to simulate some latency:

signup.service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/delay';

@Injectable()
export class SignupService {
  constructor(private http: Http) {}

  checkEmailNotTaken(email: string) {
    return this.http
      .get('assets/users.json')
      .delay(1000)
      .map(res => res.json())
      .map(users => users.filter(user => user.email === email))
      .map(users => !users.length);
  }
}

Notice how we filter for users that have the same email as the one provided to the method. We then map over the results again and test to make sure that we’re getting an empty object.

In a real-world scenario, you would probably also want to use a combination of the debounceTime and distinctUntilChanged operators as discussed in our post about creating a real time search. Introducing some debouncing like that would help keep the amount of requests sent to your backend API to a minimum.

Building the Component

Our simple component initializes our reactive form and defines our async validator: validateEmailNotTaken. Notice how form controls in our FormBuilder.group declaration can take async validators as a third argument. Here we’re using only one async validator, but you’d want to wrap multiple async validators in an array:

app.component.ts
import { Component, OnInit } from '@angular/core';
import {
  FormBuilder,
  FormGroup,
  Validators,
  AbstractControl
} from '@angular/forms';

import { SignupService } from './signup.service';

@Component({ ... })
export class AppComponent implements OnInit {
  myForm: FormGroup;

  constructor(
    private fb: FormBuilder,
    private signupService: SignupService
  ) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      name: ['', Validators.required],
      email: [
        '',
        [Validators.required, Validators.email],
        this.validateEmailNotTaken.bind(this)
      ]
    });
  }

  validateEmailNotTaken(control: AbstractControl) {
    return this.signupService.checkEmailNotTaken(control.value).map(res => {
      return res ? null : { emailTaken: true };
    });
  }
}

Our validator is very similar to a typical custom validator. Here we’ve defined our validator in the component class directly instead of a separate file. This makes it easier to access our injected service instance. Notice also how we need bind the this value to insure that it points to the component class.

Making the Validator Reusable

We could also define our async validator in its own file, for easier reuse and separation of concerns. The only tricky part is to find a way to provide our service instance. Here, for example, we create a class that has a createValidator static method that takes-in our service instance and that returns our validator function:

/validators/async-email.validator.ts
import { AbstractControl } from '@angular/forms';
import { SignupService } from '../signup.service';

export class ValidateEmailNotTaken {
  static createValidator(signupService: SignupService) {
    return (control: AbstractControl) => {
      return signupService.checkEmailNotTaken(control.value).map(res => {
        return res ? null : { emailTaken: true };
      });
    };
  }
}

Then, back in our component, we import our ValidateEmailNotTaken class and we can use our validator like this instead:

ngOnInit() {
  this.myForm = this.fb.group({
    name: ['', Validators.required],
    email: [
      '',
      [Validators.required, Validators.email],
      ValidateEmailNotTaken.createValidator(this.signupService)
    ]
  });
}

Building the Template

In the template things are really as simple as it gets:

app.component.html
<form [formGroup]="myForm">
  <input type="text" formControlName="name">
  <input type="email" formControlName="email">

  <div *ngIf="myForm.get('email').status === 'PENDING'">
    Checking...
  </div>

  <div *ngIf="myForm.get('email').status === 'VALID'">
    This email is available.
  </div>

  <div *ngIf="myForm.get('email').errors && myForm.get('email').errors.emailTaken">
    This email is not available.
  </div>
</form>

You can see that we display different messages depending on the value of the status property on the email form control. The possible values for status are VALID, INVALID, PENDING and DISABLED. We also display an error message if the async validation errors-out with our emailTaken error.

Note: Form fields that are being validated with an async validator will also have an ng-pending class while validation is pending. This makes it easy to style fields that are currently pending validation.

And there you have it! An easy way to check for validity with a backend API.

Conclusion

In this tutorial, you created an Angular application that uses a custom async validator to check if an email provided by a user does not already exist in the list of users.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Alligator.io

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel