DEV Community

Cover image for Practicing my RxJS (no subscription nesting)
Ayelet Dahan
Ayelet Dahan

Posted on

Practicing my RxJS (no subscription nesting)

I've been listening to the @AngularShow recently, and specifically to their great episodes about RxJS operators. So far two of promised four episodes have been released (one and two) and they have given me motivation to improve some especially complex bit of piping we have in our application.
The goal of this exercise was to reduce a group of subscriptions I had in my code to a single subscribe. It guarantees that all decisions about the code's flow are made in a single place, or at least, as a single flow of decisions.

Background

Content Note: I reduced some of the complexity of the real project for the sake of this post, but the general ideas are kept intact.

The app loads data from two API endpoints. The first endpoint fetches the structure of the app and the second endpoint fetches the specific values to be placed over the structure. The structure, once loaded, never changes, but the values will keep going back and forth over the API. Because the structure is static, it is kept in the StructureService memory while the dynamic values are kept in a store (ngxs, in case you're wondering).

Now here's where it starts getting interesting. There are a couple of pages in the module where the user can "land" and we always need to have the values and the structure available as soon as possible. So to keep thing DRY, we call both loading actions directly in the module's constructor.
I'll just say that I was really exited to find that I can make full use of dependency injection in the module's constructor. You can read more in Ben Nadel's post about this.

A world of Observables

We have two main observables:

  • structureComplete$ - a void observable that completes after structure data is stored in the service.
  • valuesState$ - the values from the store.

The valuesState from store

const valuesState$: Observable<Values> = this.store.select(ValuesState)
Enter fullscreen mode Exit fullscreen mode

The store loads and fires its first values immediately with the default of null, so we need to skip that and wait for some meaningful content from the endpoint. For that we're going to use the skipWhile filter. I know that when the data loads successfully, the values will never be null, so that's what I check for.

const valuesState$: Observable<Values> = this.store 
  .select(ValuesState)
  .pipe(skipWhile((values) => values !== null));
Enter fullscreen mode Exit fullscreen mode

The structure complete

The first page that is shown to the user will probably have the structureComplete$ observable still alive, so we'll concat the structure with the values.

const valuesPendingStructure$ = concat(
  this.structureService.structureComplete$ as Observable<unknown>, // to prevent the need of mapping
  valuesState$
).pipe(skipWhile<Values>((v) => !v)); // skip the 'structureComplete'
Enter fullscreen mode Exit fullscreen mode

I banged my head around this for a while, because I really don't need any data from the structureComplete$ observable. I just want to know it ... completed, and the information is safely stored in the service. All I really care about, once it's done, are the values. I know the structure is in place, but I have to keep watching the values.

The Observable<unknown> calms TypeScript's nerves and lets the valuesPendingStructure$ variable have the type of Observable<Values>. The skipWhile<Values>((v) => !v) enables us to skip the first value emitted, which comes from the structure completing.

Putting it all together

As I mentioned before, There are several pages which look at the structure observable, and while the first page to look at structureComplete$ will likely find it still loading, later pages will already see it complete and cannot subscribe to it. For that purpose I added a small isReady getter to the structure service which defaults to false, but sets to true at the same time the observable completes and the structure is stored. To decide which of the values observables should I use, I added the iif condition observable.

// Choose one of two options
// if structure is ready, get down to business
// if it isn't, wait for loadComplete$ and then hand over the values
const values$ = iif(() => this.structureService.isReady, 
  valuesState$, 
  valuesPendingStructure$)
);
Enter fullscreen mode Exit fullscreen mode

The iif operator can act both as an observable creator and as a pipe operator. In this case, we're using it as an observable creator, based on the value of isReady. If the structure has already loaded, subscribe directly to the values from the store. If we're still waiting for the structure to load, we'll use the observable that's waiting for it before switching to the values.

That's it. We can now subscribe to a single observable and always get the most up-to-date values

$values.pipe(takeUntil(this.destroy$)).subscribe({
  next: values => {
    // ...
  },
  error: error => {
    // Inform the user something went wrong
    // log the error for debugging/improving
  }
});
Enter fullscreen mode Exit fullscreen mode

Two important things to remember:

  1. We have lots of observables here, and we should always make sure to unsubscribe at some point. I really like the destroy$ pattern which completes once when the page destroys and gives us a great reference point to unsubscribe.
  2. All these observables, something is bound to go wrong. Make sure to always have an error handler on your subscription, so you can inform the user that something went wrong.

A small final note

As usual, while writing my posts, I had to break multiple time to continue working on this and other tasks. During some of those breaks I kept changing and improving the code, as well as discovering bugs and fixing them. I'm sure there are better ways to do this. For example, I think I can avoid having the structureComplete observable and use the original HttpClient observable.

If you have any additional thoughts on how this could be made better, I'd appreciate a comment!

Discussion (1)

Collapse
joellau profile image
Joel Lau

great article on observables! i think they're often overlooked as one of the weird part of the angular stack, but has great potential to ease development pains if done correctly

with regards to your final comments regarding structureComplete - I've been experimenting with store service patterns using BehaviorSubjects and have pretty good impressions thus far

let me know if you ever pick it up and what you think :D