[Angular | RxJS] BehaviorSubject with custom states

Whenever I call an api, often my frontend has to go through some different visualization steps. Imagine you have some kind of search input and a list which displays the results but you want to show an loading indicator, an error hint and something different when the result is empty.

State BehaviorSubject

This is where the StateBehaviorSubject comes in handy. First, let's have a look at the source code:

import { BehaviorSubject, Observable } from 'rxjs';

export enum BehaviorStates {
  Empty,
  Loading,
  Ready,
  Error,
}

export class StateBehaviorSubject<T> extends BehaviorSubject<T> {
  private state: BehaviorSubject<BehaviorStates> =
    new BehaviorSubject<BehaviorStates>(BehaviorStates.Empty);

  asStateObservable(): StateObservable<T> {
    const observable: any = new StateObservable<T>(this.state.asObservable());
    observable.source = this;
    return observable;
  }

  empty() {
    this.state.next(BehaviorStates.Empty);
  }

  loading() {
    this.state.next(BehaviorStates.Loading);
  }

  override next(value: T): void {
    super.next(value);
    this.state.next(BehaviorStates.Ready);
  }

  override error(err: any): void {
    this.state.next(BehaviorStates.Error);
  }
}

export class StateObservable<T> extends Observable<T> {
  public state$!: Observable<BehaviorStates>;

  constructor(state: Observable<BehaviorStates>) {
    super();
    this.state$ = state;
  }
}

The StateBehaviorSubject extends from BehaviorSubject but it adds a variable state which is a BehaviorSubject itself. To set the current state I added a method for each possible state.

To be able to work with an Observable which also contains the state, I created the method asStateObservable - this should be self-explaining.

Example usage

I use my order service for example. I normally define a private StateBehaviorSubject and a public StateObservable inside a service.

export class OrderService {
  private _orders: StateBehaviorSubject<IOrderModel[]> =
    new StateBehaviorSubject<IOrderModel[]>([]);

  orders$: StateObservable<IOrderModel[]> = this._orders.asStateObservable();

  constructor(private dialog: Dialog) {
    this.loadOrders();
  }

  loadOrders() {
    this._orders.loading();
    setTimeout(() => {
      this._orders.next([
        {
          id: '123',
          orderType: OrderType.Delivery,
          title: 'Bowl',
          restaurantUrl: 'https://www.someurl.at/',
          orderDeadline: new Date(2023, 3, 6, 10, 30),
          paymentUrl: 'https://www.paypal.com/paypalme',
          description: 'bruh, it is monday again.',
        },
      ]);
    }, 5000); // simulate loading
  }
}

Initial the subject's state is empty by default, when loadOrders getting called it changes to loading and after 5 seconds the next call changes it to ready.

Now let's display the orders in a smart component which is importing the orderService via DI.

export class SomeComponent {
  constructor(public orderService: OrderService) {}
}
<div *ngIf="orderService.orders$ | async; let orders" class="h-full w-full">
  <order-list
    [orders]="orders"
    [state]="(orderService.orders$.state$ | async)!"
  ></order-list>
</div>

The dump component order-list takes the state from the StateObservable and is always displaying the right template.

Teaser

The angular async pipe is a nice tool but sometimes we need more. In the next blog post I will show you a better way using our newly created StateBehaviorSubject. Consider subscribing to my newsletter! :-)

If you have further questions about this topic or need help with a problem in general, please write a comment or simply contact me at yesreply@georghoeller.dev :)