Advanced Forms in Ionic

Username Availability with an Asynchronous Validator in Angular



·

A little while ago I released an article named Advanced Forms & Validation in Ionic where we discussed the concept of using Validators that Angular provides for validating form fields. These validators come in two flavours: synchronous and asynchronous.

Synchronous validators can be used to perform simple synchronous checks like whether a field is empty, whether it exceeds a maximum length, or whether it matches a regex pattern.

Asynchronous validators can perform more complex validations that can’t be run synchronously, like hitting a server to check the availability of a username or email. If you do not understand the difference between synchronous and asynchronous code, I would recommend watching this video first.

A form that uses this functionality might be built using Angular’s FormBuilder like this:

this.myForm = formBuilder.group({
    firstName: ['value'],
    lastName: ['value', *validation function goes here*],
    age: ['value', *validation function goes here*, *asynchronous validation function goes here*]
});

I’ve already explained most of how this works in the previous tutorial, however, a lot of people said that they had difficulty getting the validators to work when making a request to a real server (the previous tutorial just makes a “fake” asynchronous request to a server). I also had trouble initially trying to get the asynchronous validators to delegate the server requests to a provider, rather than performing the HTTP requests inside of the validator itself.

In this tutorial, I am going to focus specifically on how to make a real request to a server for validation and also how to get the validator to use an existing function in a provider to handle the request.

Before We Get Started

Last updated for Ionic 3.4.2

We’re going to jump straight into building the asynchronous validator, and I won’t be spending any time explaining how validators work in general or how to create or use other kinds of validators. So, if you are not already familiar with using validators I would recommend reading Advanced Forms & Validation in Ionic first.

1. Create the Provider That Handles the Validation Request

The example we’re taking a look is going to make a request to a server to check whether or not a username is already taken. What actually happens on the server doesn’t matter, you can do whatever you need to do to check whether the username is available, you will just need the request to return an error response if the username is not available.

The function I’ve set up in the provider looks like this:

src/providers/auth/auth.ts

    validateUsername(username){
        return this.http.get(SERVER_ADDRESS + 'auth/validate-username/' + username).map(res => res.json());
    }

The advantage of having this function in a provider rather than building it directly into the validator is that now I can reuse this in other parts of the application if I want.

2. Create the Validator

We have a function that performs the request to the server for us, now we need to build that into our username validator. If you’ve read the previous tutorial, then you may remember that the example validator we created looked like this:

import { FormControl } from '@angular/forms';

export class UsernameValidator {

  static checkUsername(control: FormControl): any {

    return new Promise(resolve => {

      //Fake a slow response from server

      setTimeout(() => {
        if(control.value.toLowerCase() === "greg"){

          resolve({
            "username taken": true
          });

        } else {
          resolve(null);
        }
      }, 2000);

    });
  }

}

but now we are going to create a validator that looks like this

src/validators/username.ts

import { Injectable } from '@angular/core';
import { FormControl } from '@angular/forms';
import { AuthProvider } from '../providers/auth/auth';

@Injectable()
export class UsernameValidator {

  debouncer: any;

  constructor(public authProvider: AuthProvider){

  }

  checkUsername(control: FormControl): any {

    clearTimeout(this.debouncer);

    return new Promise(resolve => {

      this.debouncer = setTimeout(() => {

        this.authProvider.validateUsername(control.value).subscribe((res) => {
          if(res.ok){
            resolve(null);
          }
        }, (err) => {
          resolve({'usernameInUse': true});
        });

      }, 1000);      

    });
  }

}

One key difference here is that we have added an @Injectable decorator because we are going to be adding this validator as a provider in our application so that we can inject the AuthProvider (which provides the validateUsername function that makes a request to the server) into the validator itself.

We make the request to validateUsername inside of checkUsername and we have also added some debouncing to the server call – since the validation function will trigger every time the user types anything, the server can get hammered with unnecessary requests, so we add debouncing so that the validation will only trigger after one second of inactivity.

3. Using the Validator

Before we can use the validator, we are going to have to set it up as a provider since it is now an injectable. You will need to import and add this validator to your providers array in app.module.ts:

  providers: [
    ...
    ...
    UsernameValidator,
    ...
    ...
  ]

Once that is done, you will be able to inject the validator into whichever page you are attempting to use it on:

constructor(public navCtrl: NavController, public formBuilder: FormBuilder, public usernameValidator: UsernameValidator, public emailValidator: EmailValidator) { }

and finally, you can add the validator itself as an asynchronous validator:

        this.myForm = this.formBuilder.group({
            username: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z]*'), Validators.required]), usernameValidator.checkUsername.bind(usernameValidator)]
        });

You will notice above that we have included the asynchronous validator as:

usernameValidator.checkUsername.bind(usernameValidator)

The critical part here is that we bind to usernameValidator, this is what sets up the scope correctly. If we do not do this, then when our validator code is running the scope will be messed up and it won’t be able to access this.debouncer, for example, because the scope of this will not be the UsernameValidator when it is triggered as an asynchronous validation function.

Summary

Validators are an organised and efficient way to validate form fields in Angular, we can keep all validation logic in once place and easily manage errors, so there’s a big benefit to integrating this server validation into the structure of these validators.

What to watch next...

  • Michael Hoffmann

    Thanks good example – however ti took me some time to “complete” it (to have it really running with ionic serve). There are some things missing like a sample mockserver for the auth provider. (or adding the HTTP provider)
    I wonder with you assume that the auth server returns a JSON body. ( map function in auth provider)

  • TALL

    thanks very good job