With one Subscriber, an async list showing all items from Observable, what is the most efficient way to temporarily filter results based on user provided search criteria as a string?
The component imports TokenService and the template subscribes to tokens: tokenService.tokens | async
// ./token.service.ts
@Injectable({
providedIn: 'root'
})
export class TokenService {
private tokens$ = new BehaviorSubject<Token[]>([]);
readonly tokens = this.tokens$.asObservable();
constructor() {
this.getTokens();
}
filterTokens(q?: string) { ... }
...
}
The output should display a list of items with names loosely matching the provided search criteria. Provided the letter w (lower case) as input "Work" and "New" would display from a set of ['Work', 'Test', 'New'].
Passing the newly filtered values to the BehaviorSubject obviously makes the values unrecoverable unless cached elsewhere.
filterTokens(q?: string): void {
this.tokens$.next(
this.tokens$.getValue().filter(t => t.issuer.toLowerCase().includes(q.toLowerCase()))
);
}
The following method returns "Work" and "New" when passed tokenService.filterTokens('r') | async:
filterTokens(q?: string): Observable<Token[]> {
return this.tokens.pipe(
map(tt => tt.filter(t => t.issuer.toLowerCase().includes(q.toLowerCase())))
);
}
This however, is not dynamic based on user input.
The cleanest way to set up your filterTokens method as you have already done:
And have the consumer (usually in the component), utilize a
switchMapto call the function whenever the input value changes:If you're not familiar with
switchMap, it simply takes the incoming value and maps it to a new observable. Then it will emit the emissions from this new observable. Whenever it receives a new value (search string), it will unsubscribe from the prior "filterTokens observable" and subscribe to the new one.This means it will always emit the results from
service.filterTokens()using the most recent value ofsearchString$.Also, you can simplify your service further, by not using a BehaviorSubject at all. Just declare your tokens as an observable to the fetch the data, followed by
shareReplay:Notice we declare
tokens$with the call to fetch the data then addswitchMap(1), which means any subscribers will get the most recently emitted value upon subscription.Notice here we don't need to call
getTokens()in the constructor (which I'm assuming was doing some sort of http call, and explicitly subscribing). In general, you don't want to subscribe in your services, but rather expose observables that consumers subscribe to when needed. This keeps your data flow completely lazy, because the http call only gets made when there is a subscriber to one of the derived observables, not every time the service is injected.Notice the filter function has been broken out into its own pure function outside the class to improve readability, but of course this is optional.