Reputation: 373
At the moment I have the following setup :
In my component :
/**/
onCitySearch() {
this.citiesService.getCities(this.citySearchControl.value);
}
In my service :
/**/
getCities(term: string) {
if (term.length < 3 || term === this.lastSearchTerm || this.autocompleteAjaxLock) {
return;
}
this.autocompleteAjaxLock = true;
this.httpClient
.get<City[]>(environment.api_url + '/api/city/autocomplete?term=' + term)
.subscribe(
(cities) => {
this.lastSearchTerm = term;
this.cities = cities;
this.citiesUpdated.next([...this.cities]);
this.autocompleteAjaxLock = false;
},
error => this.autocompleteAjaxLock = false
);
}
My component is subscribed to the service cities and automatically gets notified when the search is updated. Now, with the lock system I setup, if the user types quickly my search doesnt get updated. I read around about switchMap, that should be appropriate for my use case. Where and how should I implement it ? In my component or in the service function ?
Upvotes: 0
Views: 737
Reputation: 20454
A few general things:
Instead of switchMap, the solution below uses switchScan to maintain a typeahead state.
interface TypeaheadState = {
terms: { [term: string]: City[] };
currentTerm: string;
}
readonly searchTermSubject = new Subject<string>();
readonly typeAhead$ = searchTermSubject.pipe(
debounceTime(100),
map(x => x.trim().toLowerCase()),
distinctUntilChanged(),
filter(x => x.length >= 3),
switchScan((state, currentTerm) =>
state[currentTerm]
? of({ currentTerm, terms: state.terms })
: this.httpClient
.get<City[]>(environment.api_url + '/api/city/autocomplete?term=' + term)
.pipe(map(res => ({ currentTerm, terms: { ...state.terms, [currentTerm]: res }))),
{ currentTerm: '', terms: { '': [] } } as TypeaheadState
)
),
map(state => state.terms[state.currentTerm])
);
If switchMap was used then the state would've had to exist outside of the operator. By using a x-scan operator you're able to keep everything local to the oeprator, resulting in a cleaner solution. The switchScan documentation is scant, so check the mergeScan documentation instead, as it is pretty much the same except for the switching effect.
Upvotes: 1
Reputation: 5635
Why is the autoCompleteAjax lock? Are you "afraid" of too many Http requests? Just make sure you store the subscriptions created with the this.httpClient.get(...) invocation. Then upon every subsequent call to getCities loop through the cache of subscriptions and unsubscribe them (this will cancel the Http request and/or response).
As an alternative (addition) you might want to implement a short delay upfront (before invoking getCities) and when the next key stroke comes in the queued requests have to be cancelled (which will not even reach the GetCities method). This can be done very nicely with the well praised RxJs library (that comes alongside with every modern Angular application setup).
Have a look at this Cancel a delay if same observable emits for examples of RxJs switchMap
and debounceTime
method/operator.
Upvotes: 1