3

When I press a key on a form input for some reason the async validator doesn't detect the distinctUntilChanged and it still sends API requests. for example if I press 35, delete 5 and after that add 5 again it still sends the request. this is the code: (I've tried pretty much everything but still doesn't work)

validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
    this.isSubmitBtnDisabled = true;
    return control.valueChanges.pipe(
      debounceTime(1000),
      distinctUntilChanged(),
      switchMap((value) => {
        return this.apiService.someApiRequest({ 'to_number': control.value }).pipe(
          map(res => {
            console.log(res);
            if (res.success) {
              // console.log(res);
              this.isSubmitBtnDisabled = false;
              return null;
            } else {
              // console.log(res);
              this.isSubmitBtnDisabled = true;
              return{ 'invalidCharacters': true };
            }
          }),
        );
      }),
      first()
    );
  }
2
  • Are you sure you're not calling this method on every key change? Because that will just create a new chain every time. Commented Apr 10, 2020 at 14:58
  • You are returning new Observable every time, distinctUntilChanged() won't work. Commented Apr 14, 2020 at 22:57

1 Answer 1

8

By default validateSomeNumber is called after every value change.

If you return this on every value change

return control.valueChanges.pipe(
  debounceTime(1000),
  distinctUntilChanged(),
  ...
)

you're creating a new Observable of value changes on every value change. e.g if you type four characters you end up with four independent Observables each emitting one character and not with one Observable emitting four times. So debounceTime and distinctUntilChanged will only effecting the Observable you create on a particular value change but not the value change process as a whole. If they only effect an Observable that emits once they obviously don't work as you intend them to do.

You should return the http request directly

validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
  this.isSubmitBtnDisabled = true;
  return this.apiService.someApiRequest({ 'to_number': control.value }).pipe(
      map(..),
  );
}

Limiting the request frequency

Option 1: updateOn

To prevent the http request from being executed on every value change Angular recommends changing the updateOn property to submit or blur.

With template-driven forms:

<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">

With reactive forms:

new FormControl('', {updateOn: 'blur'});

{updateOn: 'blur'} will only execute the validators when your input looses focus.

Option 2: Emulate debounceTime and distinctUntilChanged

Angular automatically unsubscribes from the previous Observable returned by the AsyncValidator if the form value changes. This allows you to emulate debounceTime with timer. To emulate distinctUntilChanged you can keep track of the last request term and do the equality check yourself.

private lastRequestTerm = null;

validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
  this.isSubmitBtnDisabled = true;
  // emulate debounceTime
  return timer(1000).pipe(
    // emulate distinctUntilChanged
    filter(_ => control.value != this.lastRequestTerm),
    switchMap(() => {
      this.lastSearchTerm = control.value;
      return this.apiService.someApiRequest({ 'to_number': control.value });
    }),
    map(..)
  );
}
Sign up to request clarification or add additional context in comments.

3 Comments

hey! thank you very much it works great. but I still don't understand why can't I use debounceTime and distinctUntilChange if they are made exactly for those reasons, why do i need to emulate it?
Your previous approach creates a new Observable of value changes on every value change. So you end up with multiple independent Observables. If you type a,b,c,d you don't get one Observable emitting a,b,c,d but instead 4 Observables one emitting a, another b, another c, another d. So operators supposed to deal with multiple incoming values obviously won't work as intended because each Observable only emits once.
Great thanks! you helped me alot to understand whats going on!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.