Programming reactive interfaces with RxJS

Building enterprise level web UI's is challenging. Functional Reactive Programming (FRP) may hold the solution with the advent of 'Reactive Extensions for JavaScript' also known as RxJS.

What is reactive programming!?

André Staltz introduction to 'reactive programming' gives a great overview to the subject, I strongly recommend you read this first.

Functional Reactive programming is a paradigm that combines event driven programming with the benefits of functional programming. UI development is a perfect use case, as the majority of engagement comes from user interaction.

Streams and monads

Event streams are the cornerstone of RxJS, however in order to explain this we must first understand monads, which is no easy task. If you've ever used libraries like jQuery, the syntax may feel familiar:

$('#id').fadeOut('slow').fadeIn('fast')

The conceptual idea of chaining functions onto monads means you can write succinct, readable code. RxJS builds on this theme by broadcasting and subscribing to event streams, functions can be chained to these event streams to perform a range of actions.

RxJS in practice

Let's put these concepts into practice. Imagine you are given a requirement to build a PIN entry feature, that must apply the following business rules:

  • Accept only numeric values from the keyboard.
  • Take a maximum of 4 numeric values.
  • Update the UI to display the user input values in order.
  • Display a notification when all criteria has been met.

Lets look at how we might implement this in RxJS:

    Rx.Observable.fromEvent(document, 'keyup')
        .debounce(100)
        .map((data) => data.key)
        .filter((key) => !isNaN(Number(key)))
        .take(4)
        .do(input => document.getElementsByTagName('input')[this.inputCount++].value = input)
        .count()
        .last(x => document.getElementById('notification').style.display='block')
        .subscribe(this.inputCount = 0);

Checkout the full example on Github

Please note this requires an ES6 compatible browser

In just 9 lines a stream has been created, which fulfils all these requirements. Lets step through the code to take a closer look at what's happening:

    // Create observer on the keyup event
    Rx.Observable.fromEvent(document, 'keyup')
        // Wait 100ms between user keyup events 
        .debounce(100)
        // Strip the keyboard keys value from the data object
        .map((data) => data.key)
        // Filter out everything but number keys
        .filter((key) => !isNaN(Number(key)))
        // Only accept 4 numerical key values
        .take(4)
        // Push this value into the N input field on the DOM
        .do(input => document.getElementsByTagName('input')[this.inputCount++].value = input)
        // Count the number of numerical keypresses
        .count()
        // On the last numerical keypress display a notification to the user
        .last(x => document.getElementById('notification').style.display='block')
        // Finally subscribe to the key up event and start the input count at 0
        .subscribe(this.inputCount = 0);

Considerations and improvements

This example implementation is not perfect. The view interaction is tightly coupled, ideally this would be handled by a mediator or other actor. Taking this idea further we could include:

  • An external API to validate the PIN value is correct
  • Error handling if the PIN does not match the expected value
  • Handling mouse and manual focus events.

Summary

RxJS has a steep learning curve, but With Netflix driving the profile of this emerging set of libraries I expect resources and documentation to improve.

Having used RxJS in conjunction with Typescript and ES6 I have been really impressed by it's versatility, that said it may not suit every use case. If you are working on enterprise level applications with larger teams 20+ RxJS is worth some investigation.

Further reading:

The full exampe RxJS code can be found on Github:
https://github.com/philipbeel/example-rxjs