Skip to content

[Angular | RxJS] BehaviorSubject with custom states

Georg Höller
Georg Höller
2 min read
[Angular | RxJS] BehaviorSubject with custom states
Photo by Clark Tibbs / Unsplash

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 :)
Angular

Comments


Related Posts

Members Public

[Angular | Storybook] Tailwind, Directives, Content Projection, Icons and i18n

Install packages npm i -D @nx/storybook npm i -D @storybook/angular Create config nx g @nx/storybook:configuration <project-name> I created the config inside the main app to share some configs/modules. My stories are located at libs/shared/ui. That's why I had to

[Angular | Storybook] Tailwind, Directives, Content Projection, Icons and i18n
Members Public

[Angular] Dynamic App Config and Translations in a Submodule

When I'm setting up a new angular application, I always start with translation support and an external app config json loader. You should never start a new app without an internationalization (i18n) system - even if you only support one language! We are going to use the package

[Angular] Dynamic App Config and Translations in a Submodule
Members Public

[Capacitorjs | Angular] Back Swipe Support for iOS and Android

Recently I recognized that capacitorjs apps does not have a native back swipe support. Little bit weird I thought but the solution is quite simple! iOS - Native To enable the iOS back swipe we need to write a capacitor plugin. I tried it without a plugin but inside the

[Capacitorjs | Angular] Back Swipe Support for iOS and Android