dinamix
dinamix

Reputation: 661

Angular form control last observable change

I am building an autocomplete function that is querying a backend for suggestions and would like to only get the last query that was made given a certain delay while the user is typing in an angular 5 form control. Currently my code looks like

 this.newVendorForm.get('address').valueChanges.pipe(delay(3000)).subscribe(
  address => {
    this.geocodeApi.getAddressSuggestions(address)
      .subscribe(
        response => {
          console.log('address suggestions');
          console.log(response);
          this.addressSuggestions = response;
        },
        error => {
          console.log('error getting address suggestion');
          console.log(error);
        }
      )
  }
);

This works however it makes a query for each typed in letter after 3000 ms. For example 'test' would query ['t', 'te', 'tes', 'test'] after 3000 ms. How can I just take the last change (i.e. 'test') from valueChanges after the 3000 ms delay and then do the subscribe? Thank you for you help

Upvotes: 1

Views: 2183

Answers (1)

Explosion Pills
Explosion Pills

Reputation: 191779

What you want is a mixture of debounceTime and switchMap.

this.newVendorForm.get('address').valueChanges.pipe(
  debounceTime(3000),
  switchMap(address => this.geocodeApi.getAddressSuggestions(address).pipe(
    catchError(err => {
      console.error(err);
      return of();
    })
  )),
  filter(Boolean),
).subscribe(response => this.addressSuggestions = response);
  • debounceTime makes it so that if there are two valueChanges emissions within 3 seconds of each other, only the last one is used. This is different than delay which will emit all of the changes 3 seconds after they were made.
  • switchMap takes an inner observable such as an http request and changes the observable stream to it -- i.e. you are now subscribed to the getAddressSuggestions observable stream. If something emits to switchMap, it will cancel the previous observable. The result of this is that if a previously made getAddressSuggestions call had not been completed before a new one starts, the previous one is canceled.
  • The catchError (lettable operator version of .catch) is used on the getAddressSuggestions observable instead of the valueChanges. Otherwise, if there were an error from the API, the valueChanges observable would complete. Using catchError where it is allows you to handle the error without completing the valueChanges observable.
  • filter is used to only emit responses that have values. In case there is an error, the of() won't be emitted. This is just one way to handle this situation though.

Finally you may want to avoid the manual .subscribe since you will have to .unsubscribe. Instead you can try to rely on the | async pipe in your template which will handle the subscription for you.

Upvotes: 5

Related Questions