How to benefit most from Reactive Extensions
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:
Outputproperties of components/directives use
Observablesunder the hood to emit events/data to parent components (the
Routeruses lots of
Observablesto communicate routing events, like
ActivatedRoute.dataand 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:
filter instead of an if statement
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
- Expressed entirely in the Rx language
As a rule of thumb, if you see an
if statement in the
subscribe callback, think about the
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.
Subject is an Observer
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
Subject know about that? We can always do this:
And then we may probably also want to complete the
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
Observable into the
Don’t forget to merge
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:
Merge will just combine all our
Observable streams into one superstream, which will emit values every time the child streams emit.
Don’t mix up the merge
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
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:
Working with Arrays
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
This has a bit more lines, but is much cleaner and done entirely in a reactive fashion.
Don’t overload the subscribe function
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:
subscribethat has an anonymous function with many lines of code is bad;
subscribethat has a named function as an argument is acceptable if nothing else can be done to simplify it using operators;
subscribethat has an anonymous function with a single line of code is good;
subscribethat has no arguments is ideal (though rare)
Don’t abuse Rx.js
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
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
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.