A classic task when you have some input field and you have to fetch something on values changes. Let's imagine we use Angular Reactive Forms. Example:
orders$ = inputControl.valueChanges.pipe(
switchMap((value) => {
return someService.fetch(value);
})
);
Now we should also somehow manage loading state. I usually use tap:
orders$ = inputControl.valueChanges.pipe(
tap(() => { loading = true }), // or loading$.next(true) if loading is a subject
switchMap((value) => {
return someService.fetch(value);
}),
tap(() => { loading = false }), // or loading$.next(false) if loading is a subject
);
However it seems we can somehow avoid assigning values in the tap and use RxJs instead.
But I could not find a way to do with it.
For me, the usage of the ideal solution would be
orders$ = <some abstraction here that depends on inputControl.valueChanges and fetching>
loading$ = <some abstraction here that depends on fetching>
If you think about the loading state as part of a larger "order search request", then it becomes easier to see how to use RxJS to create an observable that emits the desired overall state.
Instead of having two separate observables, orders$ and loading$, you could have a single observable that emits both pieces of data (since they are always changed at the same time).
We basically want to create an observable that initially emits:
{
isLoading: true,
results: undefined
}
then, after the results are received, emits:
{
isLoading: false,
results: ** orders from api call **
}
We can achieve by using switchMap, map, and startWith:
orderSearchRequest$ = this.searchTerm$.pipe(
switchMap(term => this.searchService.search(term).pipe(
map(results => ({ isLoading: false, results })),
startWith({ isLoading: true, results: undefined })
)),
);
<form [formGroup]="form">
<input formControlName="searchTerm" placeholder="Order Search">
</form>
<div *ngIf="orderSearchRequest$ | async as request">
<div *ngIf="request.isLoading"> loading... </div>
<ul>
<li *ngFor="let order of request.results">
{{ order.description }}
</li>
</ul>
</div>
Here's a working StackBlitz demo.
Take a look at my library ez-state.
https://github.com/adriandavidbrand/ngx-ez/tree/master/projects/ez-state
https://adrianbrand.medium.com/angular-state-management-using-services-built-with-ez-state-9b23f16fb5ae
orderCache = new EzCache<Order[]>();
order$ = this.orderCache.value$;
loading$ = this.orderCache.loading$;
inputControl.valueChanges.subscribe(value => {
orderCache.load(someService.fetch(value));
});
An EzCache is a glorified behaviour subject that has methods load, save, update and delete and manages state observables like loading$, loaded$, saving$, saved$ etc.
I would personally would have the cache in the service and expose the observables from the service like in that article I wrote.
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