For Java developers, JavaScript seems like an easy language to work with. It looks sort of like Java and just works. However, the more we work with JavaScript, the more complicated and convoluted it can become.
At some point your application will need to deal with events. The more events you have, the more code you’ll need to handle it. As the complexity increases, your code can start to look like a jungle of encasing curly braces.
That’s just fun for anyone – especially for another dev that might end up working on it or even worse – future you in three months’ time.
This is where RxJS comes in.
RxJS is an extremely popular JavaScript library that makes composing asynchronous and event-based programs easier through observable sequencing. What does this actually mean?
Let’s begin with the simple button click event.
Consider a button. This button will get clicked on at some point to do something. The HTML looks something like this:
<button type="button">click me</button>
The supporting JavaScript looks something like this:
var button = document.querySelector('button'); button .addEventListener('click', (event) => console.log('it works'));
Though this looks simple, dealing with events can quickly become a minefield. You can abstract it out into its own function, but that creates more code. While it’s easy to say that we should be writing less to achieve clean and elegant code? The structures need to support it as well.
Vanilla JavaScript rarely achieves this, so RxJS is introduced. RxJS comes with an object called Observable
. Within this Observable
object, you can create a container for your ‘observing’ item. It then pipelines your item into a subscriber that does something when the conditions of the subscription are met.
For example, the equivalent of an addEventListener
method in RxJS is fromEvent
. The equivalent RxJS code for the JavaScript above can look something like this:
var button = document.querySelector('button'); Rx.Observable .fromEvent(button, 'click') .subscribe(event => console.log('clicked'))
Though this can look like more code, it clearly separates out the different parts of the logic.
For example, if we wanted to prevent a click spam on our button and limit it to one accepted event every 2 seconds, in vanilla JavaScript it may look something like this:
var button = document.querySelector('button'); var rate = 2000; var lastClick = Date.now() - rate; button .addEventListener('click', () => { if (Date.now() - lastClick >= rate){ console.log('Clicked'); lastClick = Date.now(); } });
In this instance, you will need somewhere to set the rate, track the last click, and create the logic that will achieve your expected output. In contrast, if we wanted to achieve the same thing in RxJS, it would look something like this:
var button = document.querySelector('button'); Rx.Observable .fromEvent(button, 'click') .throttleTime(1000) .subscribe(event => console.log('Clicked'))
Despite having the same effect, the RxJS version is much clearer to understand. Why? because all the necessary information is structured in a funnel-like pipeline that guides you through a code story. This way you can read it in a linear fashion. So mentally it will make more sense than trying to track down the pieces and solve the mystery.
Generally speaking, RxJS is an event management system. This little-big JavaScript library reconfigures how you compose your code and groups logical data together to make it easier to mentally digest and trace.
By design, RxJS has a core type called an Observable
object. This Observable
has 4 main functions and they are creating, subscribing, executing and disposing of the observed thing.
Using the example previously displayed above, our thing is frontEvent(button, 'click')
. Our button
is the object we’re concerned with and the click is the instance event we want to target. subscribe
is an object that executes the observable
.
For RxJS, subscribe
is always required to be attached to an observable
. You can also unsubscribe
from an observable
. This means you can cancel any ongoing observable executions instigated by subscribe
.
For example, you might have something that calls an external API every 10 seconds for updates but you want this to stop when a user clicks on a button. You can do this by attaching unsubscribe
to the observable
.
An
is a set of callbacks that tracks the progress of the delivered values by the observer
observable
. It lets you decide what happens during each state and what to do if an error occurs. These progress updates are called notifications and they are next
, error
, and complete
. An RxJS observer
object usually looks something like this:
const observer = { next: x => console.log('Observer got a next value: ' + x), error: err => console.error('Observer got an error: ' + err), complete: () => console.log('Observer got a complete notification'), };
You can put whatever you want as your logic in place of console.log()
. Using the observer can look something like this:
observable.subscribe(observer);
RxJS operators are functions that let you do something to the value passed through by the observable
before it gets subscribed. In our RxJS example in the previous section, throttleTime()
is an example of an operator. It lets you add a condition to the observed event.
However, this isn’t the only operator available.
In RxJS, observable isn’t the only Observable
type. A Subject is a special type of Observable
that lets you multicast several Observables
at the same time. In order to do this, you subscribe to the same subject as many times as you want to create multiple instances of the Observable
inside the subject.
For example, inside your app, you might have something like this:
... import { Subject } from 'rxjs'; ... const subjectExample = new Subject<number>(); subjectExample.subscribe({ next: (v) => console.log(`cat goes: ${v}`) }); subjectExample.subscribe({ next: (v) => console.log(`dog goes: ${v}`) }); subject.next('meow'); subject.next('woof'); // Logs: // cat goes: meow // dog goes: meow // cat goes: woof // dog goes: woof
In the code above, you’ve called the single subject that contains two observable objects.
An RxJS scheduler controls when a subscription starts, and when notifications get delivered. It’s similar to a store of queued tasks based on priorities. It also gives your subscriber an execution context that lets the subscriber know when it’s time to execute its code.
For example, you might want to wait for another callback or things like animation frames to complete before moving onto the next part of the code.
The scheduler runs on a virtual clock and you can set it to begin the subscription right away by using .now()
.
Why does using RxJS matter for your code? It’s because reading code shouldn’t be akin to figuring out a mystery novel. It is one of the reasons RxJS is also heavily used in Angular. If you are using the framework by Google, understanding how RxJS works can also help you uncover Angular’s event handling strengths.
The major advantage of using RxJS is that it lets you control the event flows through what you assign to the observable. The library comes with an extensive set of pre-made operators that you can instantly use, cutting down on the time needed to manually write out each logical step required to achieve a particular effect.
RxJS cuts down on the amount of code you need to write whilst simplifying the process significantly through pre-made and bite-sized operators. It also creates a clear and predictable structure to how events are handled, leaving little room for rogue code to slip through, especially during testing.