Learn Reactive Programming: A Simple Calculator Example

web reactive-programming rxjs

The Web has evolved drastically in the past few years. The recent Ajax revolution has brought more asynchronous behaviors to our web applications, and the idea of Reactive Programming has thus came along to help manage and scale our applications. Programming with asynchronous data streams makes it much easier to build event-driven, resilient and responsive applications.

Today, let’s learn the basic idea of reactive programming paradigm and convert this JavaScript calculator from traditional event binding workflow to a reactive stream-driven approach using ReactiveX (RxJS).


Run on a new window.

The source code for the old traditional JavaScript calculator can be found at chen-yumin/rxjs-calculator-a. Our goal today is to rewrite the calculator using ReactiveX (RxJS). The actual implementation should be essentially the same, but the events are organized using Streams (Observables). I highly recommend you to try and accomplish this task yourself before you start looking at the stream-driven version re-written in RxJS, which can be found at chen-yumin/rxjs-calculator-b.

The Non-Reactive Way

We want our calculator to work for both mouse events, when the button is clicked, and keyboard events, when the corresponding key is pressed. Thus, when the user clicks on the ‘1’ button using the mouse, it should have the same effect as pressing the number ‘1’ key using the keyboard.

In traditional event binding, we’d typically create a function to share the implementation. Have a look of the following code. The function processKey is created so that it can be called both on mouse events and keyboard events.

var btns = document.getElementsByClassName("flex-item");
[].forEach.call(btns, function (btn) {
  btn.onclick = function() {
    processKey(btn.textContent);
  };

});

document.getElementsByTagName('body')[0].addEventListener("keypress", function(event) {
  processKey(event.key);
});

In order to share the implementation of two different event handlers, a specific function processKey is created to facilitate the code reuse. When a button is clicked, the button’s content is passed to this function, while when a key is pressed on the keyboard, the same function is called but with a slightly different type of parameter.

The Reactive Way

Reactive programming uses asynchronous data streams to offer a better way of managing data, or in this case, events. The 2 separate event handlers in this calculator example could be easily unified using streams.

const btns = document.getElementsByClassName("flex-item");
const stream$ = Rx.Observable.from(btns)
  .map(btn => Rx.Observable.fromEvent(btn, 'click')
    .mapTo(btn.textContent))
  .mergeAll()
  .merge(Rx.Observable.fromEvent(document, 'keypress')
    .pluck('key'));

First of all, the array btns is turned into a stream by using the Rx.Observable.from method. Because the source is an array, all of the button elements in btns will be emitted as a sequence. Remember, Streams are an abstraction used to model asynchronous data sources. In this example, this specific stream from btns emits all the button elements in it.

However, this is not what we want – yet. We don’t just want a stream of the button elements. What we want is the event handler each button is subscribed to, so when the button is clicked we get the corresponding value. In other words, we want a stream of the value each button represents when it is clicked.

So, how do we do this?

Let’s get back to our stream of the button elements. We can use map to turn this button element, into a sequence of click events, by using the Rx.Observable.fromEvent method, which turns an event into observable sequence. Then we can use mapTo to map the event to the corresponding output it should generate.

Note that because each button element emitted from the original stream is turned into a stream of values from button clicks, what we have now is actually a stream of streams. Yes, I know this might sound confusing, but if you see that fromEvent is actually put inside the first map operation, you’ll know what we have is not a one-dimensional stream any more. Because each element is turned into a stream, we will need a mergeAll to flatten this stream of streams, and turn it back to a single-dimensional (first-order) stream.

Here we go, now we have a stream of the values from the buttons being clicked.

The convenient thing about working with streams, is that you can easily merge it with other streams. In this example, the keyboard events stream is created with the fromEvent method, and the pluck method is then used to pick one of its attributes as the output. This stream is merged with the previous button events stream, so we end up having only one unified stream.

Now, it’s much simpler to define the behavior of these events with only one single stream. Can you now see the power of reactive programming?

Chen Yumin
Chen Yumin

Hi, my name is Chen Yumin.
I am the author of the stuff you're reading right now.

Latest Project
CoderBox.io
CoderBox.io

Connect With Me

Enjoy what you see? Follow me on:

Subscribe

Subscribe via RSS