How to do asynchronous validator in Angular 7?

Tomasz Sochacki
4 min readJan 9, 2019

--

Asynchronous validators are very useful feature for angular reactive forms, especially when we need to perform request to external API. A common use case, when we create form for registering new user and we want check availability of user login proposed. We can do requests to server while user is typing his proposal on input field.

In Angular 7 we have two kind of forms — template driven forms and reactive forms. The second style give us very useful and curious features like sync and async validators. In general when we use reactive form, we create input fields as FormControl objects.

Each FormControl constructor accepts one, two or three parameters. First parameter is the initial value for input, which by default equals null . Second is synchronous validator as single function or array of functions. The most interesting for us is third param — asynchornous validators, also as one function or array of functions.

Synchronous and asynchronous validators

When we use reactive forms, Angular automatic passes instance of FormControl to each async validator. Validator is a function which returns Observable object when some error occurs or null value when validation passed positive. This behaviour is similar to synchronous validators.

As a rule of thumb, when we use various validations with external API we want to avoid too much unnecessary requests. This is most important point in validation and in reactive forms we get good solution for this problem. Asynchronous validators will invoke only when all of synchronous validators passes positive. If at least one sync validation function returns fail, neither async validator will invoke.

Let’s create our first asynchronous validator

Let’s assume that we want to do simple registration form with input field for user login proposed. We assume that we have got Angular service, which makes http request to external API for checking availability of user proposed login. We want to user gets this information while typing, without necessity of clicking in some additional button etc. User convenience should be important issue for us.

First step — simple reactive form

Let’s start with creating form as typical Angular component:

@Component({
selector: 'app-register-form',
templateUrl: './register-form.component.html',
styleUrls: ['./register-form.component.scss']
})
export class RegisterFormComponent {
public form: FormGroup;

constructor() {
this.form = new FormGroup({
login: new FormControl(null, Validators.required)
});
}

isLoginToken(): boolean {
return this.form.get('login').hasError('loginExist');
}
}

Now, let’s create a simple HTML template:

<form [formGroup]="form">
<label for="login">Login:</label>
<input id="login" formControlName="login">
<span *ngIf="isLoginTaken()">
Login is already taken.
</span>
</form>

We have used one synchronous validator (marked as required) without initial value.

Next step — service and simulate http request

Now let’s create simple service for checking availability of login. In real application we use HttpClient module for preparing real request to API, but now, for simplify we’ll simulate request. To do it, we use rxjs method of and operator delay in order to delaying our sham response for better simulate real http request.

@Injectable()
export class AuthService {

constructor(private http: HttpClient) {}

public checkLogin(login: string) {
// simulate http.get()
return of({ isLoginAvailable: login !== 'test'})
.pipe(delay(400));
}
}

Create asynchronous validator

Validator will be a simple JavaScript function which accepts two arguments: reference to our AuthService and time to delay checking (as type number in milliseconds).

export const loginAsyncValidator = 
(authService: AuthService, time: number = 500) => {
return (input: FormControl) => {
return timer(time).pipe(
switchMap(() => authService.checkLogin(input.value)),
map(res => {
return res.isLoginAvailable ? null : {loginExist: true}
})
);
};
};

I assume you can use rxjs operators like switchMap or map so let’s consider what’s happen in this function. First off note that our function returns another function, which gets instance of FormControl and use it in checkLogin function.

Look at two very important rxjs operators: timer and switchMap. We’ve used timer to avoid creating request for each input onchange event. By default timer equals 500ms but it’s configurable parameters in our async validator. Input HTML element dispatches event onchange for each letter which user wrote. It may be not good solution, especially when user is very fast and can type more letters in 500–1000ms. Remember, each request is some server burden.

The second important operator is switchMap. This operator let us canceling pending requests if new stream occurs. This is good practice to use switchMap when we preparing real http requests and it save us from race condition.

Last step — how to use our async validator?

Ok, if our validation function is ready, we can use it in registration form:

@Component({
selector: 'app-register-form',
templateUrl: './register-form.component.html',
styleUrls: ['./register-form.component.scss']
})
export class RegisterFormComponent {
public form: FormGroup;
constructor(private auth: AuthService) {
this.form = new FormGroup({
login: new FormControl(
null, Validators.required, loginAsyncValidator(this.auth))
});
}
isLoginToken(): boolean {
return this.form.get('login').hasError('loginExist');
}
}

We’ve added third param for login’sFormControl and used Dependency Injection to add AuthService to our component. In component template we have got span element to displaying error when user proposal login is taken (our async validator returns false for login equals test):

<span *ngIf="isLoginToken()">
Login is already taken.
</span>

We can also use another way to pass validators to FormControl:

password: new FormControl(
null,
{
validators: [Validators.required],
asyncValidators: [loginAsyncValidator(this.auth)]
)
)

Now we pass two params — null as initial value and object with two fields for sync and async validators. This approach has one important advantage — if we have only async validators, we don’t have to pass empty array as second param for FormControl (to pass empty array of synchronous validators).

Summary

Asynchronous validator are very useful when we need to use http requests for checking some values. It’s important to save our API from too much unnecessary requests as shown in this article. Using async validators your application may become more user friendly.

Above example is available on my GitHub: https://github.com/drogimex/custom-async-validator

--

--