If my typeahead gets an empty search result, any subsequent query with a norrowed down search query should be prevented. E.g. if the search for 'red' returns empty, a search for 'redcar' makes no sense.
I tried using pairwise() and scan() operator. Code snippet:
import { tap, switchMap, filter, pairwise, scan, map } from 'rxjs/operators';
this.searchForm.get('search').valueChanges
.pipe(
switchMap( queryString => this.backend.search(queryString))
)
.subscribe()
Update Given a simplified scenario: There is only the term 'apple' in the backend. The user is typing the search string (the request is not aborted by the switchMap()):
The backend calls for 7. and 8. are unnecessary, because 6. already returns EMPTY. Therfore any subsequent call could be omitted. In my opinion some memoization is needed.
I would like to prevent unnecessary backend calls (http). Is there any way to achieve this in rxjs?
This is an interesting use-case and one of a very few situations where mergeScan is useful.
Basically, you want to remember the previous search term and the previous remote call result and based on their combination you'll decide whether you should make another remote call or just return EMPTY.
import { of, EMPTY, Subject, forkJoin } from 'rxjs';
import { mergeScan, tap, filter, map } from 'rxjs/operators';
const source$ = new Subject();
// Returns ['apple'] only when the entire search string is contained inside the word "apple".
// 'apple'.indexOf('app') returns 0
// 'apple'.indexOf('apple ap') returns -1
const makeRemoteCall = (str: string) =>
of('apple'.indexOf(str) === 0 ? ['apple'] : []).pipe(
tap(results => console.log(`remote returns`, results)),
);
source$
.pipe(
tap(value => console.log(`searching "${value}""`)),
mergeScan(([acc, previousValue], value: string) => {
// console.log(acc, previousValue, value);
return (acc === null || acc.length > 0 || previousValue.length > value.length)
? forkJoin([makeRemoteCall(value), of(value)]) // Make remote call and remember the previous search term
: EMPTY;
}, [null, '']),
map(acc => acc[0]), // Get only the array of responses without the previous search term
filter(results => results.length > 0), // Ignore responses that didn't find any results
)
.subscribe(results => console.log('results', results));
source$.next('a');
source$.next('ap');
source$.next('app');
source$.next('appl');
source$.next('apple');
source$.next('apple ');
source$.next('apple p');
source$.next('apple pi');
source$.next('apple pie');
setTimeout(() => source$.next('app'), 3000);
setTimeout(() => source$.next('appl'), 4000);
Live demo: https://stackblitz.com/edit/rxjs-do457
Notice that after searching for "apple " there are no more remote calls. Also, after 3s when you try searching a different term "'app'" it does make a remote call again.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With