Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add debounce time to an async validator in angular 2?

This is my Async Validator it doesn't have a debounce time, how can I add it?

static emailExist(_signupService:SignupService) {
  return (control:Control) => {
    return new Promise((resolve, reject) => {
      _signupService.checkEmail(control.value)
        .subscribe(
          data => {
            if (data.response.available == true) {
              resolve(null);
            } else {
              resolve({emailExist: true});
            }
          },
          err => {
            resolve({emailExist: true});
          })
      })
    }
}
like image 598
Chanlito Avatar asked Apr 28 '16 15:04

Chanlito


3 Answers

Angular 4+, Using Observable.timer(debounceTime) :

@izupet 's answer is right but it is worth noticing that it is even simpler when you use Observable:

emailAvailability(control: Control) {
    return Observable.timer(500).switchMap(()=>{
      return this._service.checkEmail({email: control.value})
        .mapTo(null)
        .catch(err=>Observable.of({availability: true}));
    });
}

Since angular 4 has been released, if a new value is sent for checking, Angular unsubscribes from Observable while it's still paused in the timer, so you don't actually need to manage the setTimeout/clearTimeout logic by yourself.

Using timer and Angular's async validator behavior we have recreated RxJS debounceTime.

like image 190
n00dl3 Avatar answered Oct 23 '22 16:10

n00dl3


Keep it simple: no timeout, no delay, no custom Observable

// assign the async validator to a field
this.cardAccountNumber.setAsyncValidators(this.uniqueCardAccountValidatorFn());
// or like this
new FormControl('', [], [ this.uniqueCardAccountValidator() ]);
// subscribe to control.valueChanges and define pipe
uniqueCardAccountValidatorFn(): AsyncValidatorFn {
  return control => control.valueChanges
    .pipe(
      debounceTime(400),
      distinctUntilChanged(),
      switchMap(value => this.customerService.isCardAccountUnique(value)),
      map((unique: boolean) => (unique ? null : {'cardAccountNumberUniquenessViolated': true})),
      first()); // important to make observable finite
}
like image 34
Pavel Avatar answered Oct 23 '22 16:10

Pavel


Angular 9+ asyncValidator w/ debounce

@n00dl3 has the correct answer. I love relying on the Angular code to unsubscribe and create a new async validator by throwing in a timed pause. Angular and RxJS APIs have evolved since that answer was written, so I'm posting some updated code.

Also, I made some changes. (1) The code should report a caught error, not hide it under a match on the email address, otherwise we will confuse the user. If the network's down, why say the email matched?! UI presentation code will differentiate between email collision and network error. (2) The validator should capture the control's value prior to the time delay to prevent any possible race conditions. (3) Use delay instead of timer because the latter will fire every half second and if we have a slow network and email check takes a long time (one second), timer will keep refiring the switchMap and the call will never complete.

Angular 9+ compatible fragment:

emailAvailableValidator(control: AbstractControl) {
  return of(control.value).pipe(
    delay(500),
    switchMap((email) => this._service.checkEmail(email).pipe(
      map(isAvail => isAvail ? null : { unavailable: true }),
      catchError(err => { error: err }))));
}

PS: Anyone wanting to dig deeper into the Angular sources (I highly recommend it), you can find the Angular code that runs asynchronous validation here and the code that cancels subscriptions here which calls into this. All the same file and all under updateValueAndValidity.

like image 45
Andrew Philips Avatar answered Oct 23 '22 16:10

Andrew Philips