What if you need to validate a form input whose value depends on other form inputs ? And what if the validation itself depends on an XHR request ?

Click here to share this article on LinkedIn »

Angular makes it easy with Observables and RXJS. Angular’s reactiveForms returns an Observable that emits whenever a form’s input value changes :

let input1$ = this.form.controls.input1.valueChanges;

let input2$ = this.form.controls.input2.valueChanges;

let input3$ = this.form.controls.input3.valueChanges;

In this example I’ll be Observing three form inputs in particular. This is needed because the value that I need to validate depends on these three inputs. For example, validating the travel time of a vehicle from one address to another address within your city. The value that I want to validate is travel time (input1). The value of the origin address is input2 and the value of the destination address is input3.

First, lets create an Observable that emits every time our form inputs change. By using “combineLatest” along with the three Observables I obtained above, I can create an Observable that emits a value when any of these three inputs change, providing their latest value at all times.

combineLatest(input1$, input2$, input3$)

.do(([input1, input2, input3])=>{

this.form.controls.input1.clearAsyncValidators();

})

.debounceTime(500)

.subscribe(([input1, input2, input3]) => {

if (!input1 || !input2 || !input3) return null;

let request = {

input1,

input2,

input3

}

const inputValidatorFn = this.getValidatorFn(request);

this.form.controls.input1.setAsyncValidators(inputValidatorFn);

this.form.controls.input1.updateValueAndValidity();

});

I then subscribe to this Observable, after debouncing its broadcast rate so that I only get notified when the user stops typing. My subscription supplies me with the three latest values I need. The clearAsyncValidators method is there to prevent the XHR request from being sent before the user stops typing.

Because the validation in question takes place in a server, we can make use of Angular’s setAsyncValidators function. It takes a parameter of type AsyncValidatorFn. A function of this type can return a Promise or Observable that resolves to null when there is no validation error, or resolves to an object that describes the validation error. So we construct a a function of type AsyncValidatorFn that will make an XHR request to the server, and it will come back with a verdict, after which it will resolve with null, or resolve with an object that represents a validation error.

private getValidatorFn(request): AsyncValidatorFn {

return ( control: FormGroup ) => {

return new Promise((resolve, reject) => {

this.someService.validateTravelTime(request)

.subscribe((serviceResponse) => {

let response = serviceResponse.json();

if( response && response.data.valid ) return resolve(null);

if( response && !response.data.valid ) {

resolve({ err : response.data.errorDescription })

}

})

.catch(err=>handleError(err));

})

}

}

Our validator function above makes use of a service that makes the actual XHR request. The service returns an Observable that emits the response when the XHR request fulfills.

Every time any of the inputs change (input1, input2, or input3) we will get their latest values, create a new request object, and then create a new function of type AsyncValidatorFn, which in turn we pass to Angular’s setAsyncValidators method that specifically attaches this async validator to input1. So every time an input changes (input1, input2, or input3) we check the validity of input1, asynchronously.

Finally, we use :

this.form.controls.input1.updateValueAndValidity();

This triggers a validation check after our async validator is in place.

Full Code: