Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reactive Loading state management with RxJs

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>
like image 982
ectuser Avatar asked Apr 17 '26 22:04

ectuser


2 Answers

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.

like image 197
BizzyBob Avatar answered Apr 20 '26 11:04

BizzyBob


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.

like image 23
Adrian Brand Avatar answered Apr 20 '26 12:04

Adrian Brand