Rx.js: Best Practices

Armen Vardanyan
Oct 5, 2019 · 7 min read

How to benefit most from Reactive Extensions

If you have been using Angular for more than a week, you most definitely have come across Rx.js, the reactive extensions framework for JavaScript.

An API for asynchronous programming
with observable streams (from reactivex website)

And of course you should already know that lots of stuff in Angular works using Rx.js under the hood. Here are some prominent examples:

  • The HttpClient module utilizes Observables to handle Http responses
  • The Output properties of components/directives use Observables under the hood to emit events/data to parent components (the EventEmitter class)
  • Router uses lots of Observables to communicate routing events, like Params / ParamMap, ActivatedRoute.data and so on.

So, in fact, Rx.js is a key component of the Angular ecosystem, and having to use it on a daily basis is an inevitable part of an Angular developer’s life.

This article is going to explain how to handle some common use cases in Rx.js with Angular (and not only) in an accurate manner. So, let’s get started:

Take a look at this code:

It’s pretty straightforward: take numbers from 1 to 4, multiply by 3, and log the even ones to the console. But we can do better than this: replace the if statement with a filter operator. Here:

Now this has several advantages:

  • Looks clearer
  • No clutter in the subscribe method
  • Expressed entirely in the Rx language

As a rule of thumb, if you see an if statement in the subscribe callback, think about the filter operator.

Continuing the previous point — you might think, okay, but what if I have an if statement followed by an else statement? Like this:

So you might think “if I have to do different things with odd and even numbers, I would have to write an if statement, right?”. But the answer is still no. Meet the partition operator! This one allows us to split our stream into tцo parts depending on a condition: Thus, the odd numbers will go to one Observable, and the even ones will go to another:

So here is what partition does: It takes the observable that we want to split in half as the first argument, and as the second argument it takes a function, which will divide the stream in half: the emissions that contain an even number will go right, and the others will go left. The partition function then returns an Array which consists of two Observables, the first is the emissions that satisfy the predicate, and the second is those that don’t.

At first sight this may seem like a lot trouble to do for just removing an if-else statement, but in reality, this solution is far more explicit. Whenever anyone sees the partition function, it will become obvious to them that the Observable stream is going to be split in half, and the condition by which it is split is readily available and easy to find.

If you read the official definition of a Rx.js Subject, you will notice it says “a Subject is both an Observable and an Observer”, which is somewhat a vague thing to say. Here is what it means: imagine you have a stream of events from the DOM, say, click events, and you perform some side effects, like logging, and you also have a global stream of different events from the DOM, and after you perform those side effects, you want to pass that click event to that global stream (which is a Subject). Here is the code:

Essentially this reads like this: take every mouse click, do a side effect, and then dump all of them into another stream. As far as we are concerned, this accomplishes our goal, but what if an error occurs in the performSomeSideffect function? How will the allEvents$ Subject know about that? We can always do this:

And then we may probably also want to complete the allEvents$ Subject if the clicks$ completes, and do this:

Which does not even remotely look neat and easy to comprehend. But in fact, we could just do this:

Which literally means “dump everything from the clicks$ Observable into the allEvents$ Subject”.

This one is pretty simple: if you want to subscribe to different events but do the same thing with all of them, instead of this:

Do this:

Merge will just combine all our Observable streams into one superstream, which will emit values every time the child streams emit.

As I mentioned above, Angular’s HttpClient entirely works on Rx.js. If you are familiar with Promise.all, then after reading my previous point about merge you might think “yeah, this is what I will use to get multiple values at once, instead of Promise.all”. But that is wrong. Promise.all takes an Array of Promises, waits until all of them resolve (or at least one of them to reject), and then returns an Array of results if no exceptions occur. If you do this:

You will be disappointed, because merge will fire up every time a response is received, not as soon as all of them complete. We are looking in fact for forkJoin, which is exactly the same as Promise.all, but for Observables:

Here our handleResponses method will receive an array of responses only when all of the HTTP calls succssesfully complete.

Type inference is a concept which allows the TypeScript compiler to make sense of types without explicit declarations. Take a look at this code:

As you see, in the callback provided to the subscribe method the argument type is explicitly mentioned: Order<string>. It might seem reasonable, but now take a look at this variation:

This looks like a lot of unnecessary clutter. In reality, TypeScript can easily infer the types even if we don’t mention them at all:

And VSCode IntelliSense has no problem autocompleting for us:

Even if we map to something else, the type inference will know:

Imagine that you receive a response from a service which contains a list of items — an Array. Now imagine we have to do something with this list — for example, map it to another list. Take a look at this code:

In this code take the items from the response, and handle the transformation of the items inside out subscribe function. But in facts, Arrays can also be expressed as streams, so we can handle this better, using switchMap to transform the Array into an Observable, transform each emitted item and then recollect them using toArray:

This has a bit more lines, but is much cleaner and done entirely in a reactive fashion.

As you’ve read, I suggested removing if-else blocks and Array handling/loops outside of the subscribe method and back into the Rx.js operators. In fact, here is a tweet that got me thinking:

So, a rule of thumb:

  • A subscribe that has an anonymous function with many lines of code is bad;
  • A subscribe that has a named function as an argument is acceptable if nothing else can be done to simplify it using operators;
  • A subscribe that has an anonymous function with a single line of code is good;
  • A subscribe that has no arguments is ideal (though rare)

Rx.js is lots of fun and can be really helpful, but, as with every tool, it has limitations as to where it should be used. Sometimes folks get too excited about Rx.js and try to put Observables and Subjects wherever possible; in reality, this can make code almost unreadable. Take a look at this component:

Here there are two Subjects being used to convey a form submit event and handle the HTTP call for it. If you think that I am exaggerating and no one writes code like this, I have bad news for you: I have seen dozens of developers do things like this, wrap everything into Subjects and justify it by “what if we need to do something with this flow later” or “it is better the reactive way”. But in reality, this makes code very hard to read and almost impossible to comprehend. I had an experience when ee literally could not fix a bug because we could not find where it originated; it got lost in a web of interconnected next and subscribe calls. So, if you are thinking whether you should or should not use Rx.js in a particular instance, here is a rule of thumb. Ask yourself the following questions:

  • Can I implement this without using Rx.js at all?
  • Will using Rx.js make the future readers of this code scroll through this file or event visit other files?
  • Do I use Rx.js only in anticipation of something “reactive” showing up later, but I don’t really need it now?

If the answer to at least two of these questions is “yes”, than you really should reconsider using Rx.js at that point.

Rx.js is a very powerful tool, with lots of stuff to learn and lots of possible applications. As with any great tool, it is important to understand how to use it properly now not to end up with low quality code and lots of hard-to-fix bugs in the future.

Angular In Depth

The place where advanced Angular concepts are explained

Armen Vardanyan

Written by

Senior Angular developer from Yerevan, Armenia

Angular In Depth

The place where advanced Angular concepts are explained

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade