Reputation: 4766
I have a method that will search for employee's based on a search term.
this._sub.pipe(
debounceTime(500),
filter(x => !!x),
distinctUntilChanged(),
switchMap(this.getResults.bind(this))
).subscribe((d: IDisplayEmp[]) => {
console.log('RES', d)
this.displayResults = d;
});
this._sub.next('mySearchTerm');
This works great, previous API calls are cancelled if a new one is issued. However here comes the issue.
Active API calls are only cancelled when the debounce has emitted. If an active API call returns whilst the debounce is waiting it still triggers my subscribe.
I understand this is because the cancelling only happens within the scope of the switchmap.
Is possible to refactor this so that if the debounce is waiting for input to stop it will cancel any pending API calls?
Probably a naive approach but i attempted something like below, it emits values but now there is no debounce in effect.
this._sub.pipe(
filter(x => !!x),
distinctUntilChanged(),
switchMap((x) => {
return of(x)
.pipe(
debounceTime(500),
this.getResults.bind(this)
);
})
).subscribe((d: IDisplayEmp[]) => {
console.log('RES', d)
this.displayResults = d;
});
Thanks for any help.
Upvotes: 7
Views: 9614
Reputation: 965
The accepted answer should work fine, but I wanted to share another way you can do this with timer
instead of debounceTime
:
this._sub.pipe(
filter(x => !!x),
distinctUntilChanged(),
switchMap(val => timer(500).pipe(
switchMap(() => this.getResults(val))
))
).subscribe(...);
Now, if an emissions reaches switchMap
, the timer
and everything in it (including an active network request) will be canceled, and a new timer
will be created.
Upvotes: 9
Reputation: 121
Unsure here, need to be explored, but you could transform the _sub into two observables, one will be debounced and returns the original query, the second is not debounced and returned null/undefined.
Then you merge them both and call the switchMap.
The undebounced observable should allow you to cancel the call because it will reach the switchmap too. In with case the switchMap could return an empty or never observable. And you recreate the API call only when it's the debounched observable that came in with the value.
Something like that?
const debounced = this._sub.pipe(debounceTime(500), filter(x => !!x), distinctUntilChanged());
const undebounced = this._sub.pipe(map(_ => undefined));
merge(debounced, undebounced).pipe(
switchMap(val => {
if (val) {
return this.getResults(val);
} else {
return never;
}
})
);
Upvotes: 2
Reputation: 14099
Use takeUntil
to cancel your requests whenever this._sub
emits.
this._sub.pipe(
debounceTime(500),
filter(x => !!x),
distinctUntilChanged(),
switchMap(searchTerm => this.getResults(searchTerm).pipe(
takeUntil(this._sub)
))
).subscribe((d: IDisplayEmp[]) => {
console.log('RES', d)
this.displayResults = d;
});
Upvotes: 10
Reputation: 10127
I believe this should work for you:
Use concat
so the second emission waits for the first one to complete.
const getFromAPI = (key: string): Observable<number> => {
return;
};
const keys: Observable<string> = new Observable();
concat(
keys.pipe(
debounceTime(500),
switchMap(key => getFromAPI(key)),
take(1)
)
).subscribe(console.log);
Another example:
const getFromAPI = (key: string): Observable<number> => {
return;
};
const keys: Observable<string> = new Observable();
const debouncedKeys = keys.pipe(debounceTime(500));
concat(
debouncedKeys.pipe(
switchMap(key => getFromAPI(key)),
take(1),
takeUntil(debouncedKeys)
)
).subscribe(console.log);
Upvotes: 2