15 RxJS *Awesome* Tips from 15 Sentinels
RxJS is by far the hottest library for reactive programming in the web for the past few years. It is built on fascinating concepts and paradigms which are worth digging into that aren’t in scope of this post (what is “reactive”, “declarative”, functional programming concepts and more..).
Our team heavily uses RxJs on our large scale application and on libraries we write, and it definitely makes our code beautiful and easier to read. In this post, we’ll share with you some tips that we, the Frontend group at SentinelOne, think are really practical.
Disclaimer: Some of the code examples are a little bit advanced and require familiarity with RXJS. This could be a good opportunity to learn the basics and return to this post afterwards 👍
Let’s Go!
1. Close that stream by Irena Veksler 🚪
Following is a practical example of canceling a prior subscription.
One of the most powerful features using streams is the ability to complete the subscription when there’s no need for it.
In the example, we see a method which is periodically querying a service for a specific response and, when it receives it, stops the stream by unsubscribing the observable using the takeUntil
operator.
Inspiring! Thanks Irena!
2. Act on transitionend event by Amir Barak
CSS transitions allow you to change the specified property (e.g height, width…) values smoothly.
In an Angular application, you may find it practical to have a directive that will listen to a TransitionEnd (or Start) event of any given property so you could react when it occurs.
- On the directive ngOnInit() hook, we invoke the register TransitionEnd(..) method.
- Rxjs
fromEvent
listens to DOM events of the host element and in case of a certain property change trigger, the directive will emit an event that will be handled by the wanted component.
Elegant use case! Thanks Amir.
Amir particularly likes the RxMarbles learning source — you should check it out!
3. DIY — Reactive state service by Oz Gonen
Oz is a real fan of clean code. He likes his code dry 🍸. He has a high standard for organizing it, so he created a reactive state service to manage certain parts of his code where different components need to get updated by a change made by other components.
- The Store holds a state which is a BehaviorSubject thus, it requires an initial value.
- The class that will extend this service could expose an API of a specific state management, e.g. managing a state of a list-items component that has an add / remove / update action which updates the state accordingly.
Thanks Oz! I really like it :)
4. I promised I’ll switch to new Observable by Liron Hazan (myself)
Usually when working with Web APIs or external libraries which return a Promise, you want to stay aligned with your “reactive” application flow.
Use the defer operator to return a new Observable whenever your function is called.
Same goes for when developing a Typescript lib that will be used by any Angular applications — it is good practice to expose an API that returns an Observable. 🎁
5. Level up with implementing polling by Ofir Fridman 🚀
The following example is a bit complicated but really awesome!
We’ll create a stream by using of(‘’). The delay operator will delay the subscription by the given ms. In tap we’ll trigger the next http request, and by using skip we ensure that we won’t get the initial ‘’ as value.
The next line uses concat operator to combine our http request with the polling subject and will promise that the backend will be polled with our request.
You can see it all in the demo app ! Thanks Ofir, that’s a killer tip indeed :)
6. Don’t let that error stop you️! by Assaf Azulai 🚦
In the following example, we’ll show how to handle errors in case we have several interactions and each interaction will fire a next call to a shared subject which will trigger a data fetch of a certain API.
The catchError finalizes the observable and the subject will no longer make any API calls.
By using the switchMap
operator and piping the catchError
to catch the inner error we’re still keeping the stream running for the next user interactions.
7. Let’s memo the polled response by Yoni Argovi
Advanced technique alert 🏆
The following is an optimization of the earlier polling example.
By caching the polled response and using it with the filter
operator, the emitted output will only happen when the data changes. It means that we’ll update the view only upon changes, which is much better for performance!
Yoni mentioned that there are some pitfalls we should be aware of like comparing large strings and storing large structures in the memory of our program, but if you use it wisely that’s worth saving the rendering of some large components.
— Thanks Yoni for a great idea!
8. Fire once by Doron Sever 🔥
Sometimes you see in your network tab the same API call that has been fired twice (or more).
A lot of those times it’s hard to sync between them, but we still don’t want to DDoS our server.
The auditTime
is perfect to avoid those situations.
9. Free text search By Lior Levy🔎
The following example is quite a common use case, so it’s important to do it properly.
Let’s say you need to implement a free-text-search mechanism to be used in one of your input components (or just a strip input element).
You won’t want to query the API upon every user typing/deletion etc.
For this case the combination of debounceTime
and switchMap
is perfect.
The debounceTime
operator will drop values that occur too frequently and the switchMap
will cancel the prevues inner observable and will switch to the newer emission — thus using those two will decrease a redundant load on the API.
Thanks Lior this is very practical!
10. Create your own Some operator by Igor Paevshik 💪
Igor created the “some” custom operator which is similar to the Array.some()method.
An operator is an higher-order function, meaning it could accept a function as an argument and it could also return a function.
Our operator accepts a predicate function, predicate is a function which either returns true or false.
Our operator also returns a function.
The returned function accepts a source observable which returns an observable, thus when invoking the operator in a pipe we’re actually unboxing an observable which holds the result of the operator operating on our source observable!
I hope I didn’t make it sound so complicated. You can play with the runnable example in replit :)
Thanks Igor for an inspiring snippet!!
11. Filter out null/undefined by Gal Falah ☕
This one is really straightforward.
Let’s say you have somewhere an action that could only resolved to True or False and you want to guard a state in which from any reason (such as first initialisation or a bug) the emitted value is null or undefined, In this case it’s critical cause if the result is False you want it to be a “real” false and not a falsy value that hides a bug for us.
This could be achieved by supplying the filter
operator the following "notNullish" function -BTW you can make a custom operator of it for multiple use cases :)
Inspired from ?? operator :) Rocking tip Gal!
12. Safely Join multiple results of multiple observables by Vadim Krimsky 💲💲
Let’s say you need to prepare some data that returns from an API but you need all the results at once, even if one had failed, something like Promise.allSettled().
This could be achieved by using the forkJoin
operator combine with catchError.
forkJoin
will subscribe for each inner observable and will emit the grouped results when all complete.
13. fs-rx registering to file removal event DIY by Sivan Israelov and Koral Ben Ami 📁
The following tip is about using rxjs powers when operating on the file system using nodejs/deno.
Let’s say we want to monitor a certain file removal and update a certain entities.
We can do so by creating a FileRemoveInteractor that will be used as an observer and as notifier using a Subject as follows:
14. Single producer multiple consumers — avoid race conditions 🏁
The following tip continues the above tip.
Notice that we only multicast by using a single producer, we recommend keeping this practice to reduce the chance to have a race condition and not create another instance that could initiate a removal of the same file — a case which can cause a bug.
You can play with the runnable example as well.
15. Reach completion while true by Matan Sanders 🎮
This tip is great when you know you want to stop emitting / listening as a result of a certain condition, e.g. ending a game loop, populating a progress bar etc.
In the example, we see a use of the takeWhile
operator.
As long as the counter is less than 100ms (true) a value will be emitted, and once reach false the stream will be complete.
Nice tip Matan!
Summing it up
Writing down those examples was definitely a great and learning experience.
While writing the post I found myself digging into some operators source code in order to really understand what happens under the hood and that’s always a fun thing to do.
I was really thrilled that the entire frontend group was engaged into my process of writing this post, I had the most interesting conversations with my teammates privately around some snippets, and I’m glad to work with such talented group of people — show me your code and I tell you who you are! (just kidding).
BTW — we’re hiring! 🦄🦄🦄 For the full list of open positions follow this link