The Marketing Technologist.

We talk about analytics, code, data science and everything related to marketing technology. Backed by the tech geeks of Greenhouse Group.

Create a simple toggle button using reactive programming

I'm pretty familiar with asynchronous programming with callbacks and, more recently, Promises, but I've only started playing around with reactive programming a few weeks ago. More and more people seem to be talking about it, and it looks like the concept will be available in ES7 in some form as well.

Reactive programming is programming with asynchronous data streams. You are able to create data streams of anything (clicks, hovers, network requests, intervals, etc). Literally everything. At first glance, this concept looked too complex and abstract for the stuff I usually write.

Things got a lot clearer after reading this excellent introduction to reactive programming. If you are new to reactive programming, I'd recommend reading it. When I'm learning a new concept or framework, I try to use it in an actual project as soon as possible. I also learn by writing about it, hence this post ;).

The first use-case I came across was the good old toggle button. You probably know it: initially, an element is hidden, but after you've clicked a button, it becomes visible. When you click the button again, the element disappears. Pretty easy, right? So I decided to write it using streams.

I've read some good stories about RxJS, so I used that library. Another good option is Bacon.js. The principles are the same, so for this toggle button example, it doesn't really matter which one you use.

Toggling using DOM events, the 'old way'

I started writing the code the way I was used to. Just register an event handler on the button, and switch the visibility of the panel in the callback. This code should look pretty familiar and is easy to understand:

const toggleButton = document.querySelector('.toggleButton');  
const toggleablePanel = document.querySelector('.panel');  
const buttonInitialState = false;

const togglePanel = () => {  
  const panelIsInvisible = toggleablePanel.style.display === 'none';
  toggleablePanel.style.display = panelIsInvisible ? 'block' : 'none';
};

if(!buttonInitialState) {  
  toggleablePanel.style.display = buttonInitialState ? 'block' : 'none';
}

toggleButton.addEventListener('click',togglePanel);  

You see this in action at https://jsbin.com/wewesuzaga. There is nothing wrong with it, and doing it this way is totally acceptable. But seeing our good old friend the addEventListener in action wasn't the reason you got here, right? So, let's...

.. bring in the streams

Our variable declaration at the top of the snippet can remain the same. We start by creating a stream of clicks, using Rx.Observable.fromEvent. Remember the mantra of reactive programming: everything is a stream!

// Create a stream of clicks on the Toggle button
const clicks = Rx.Observable.fromEvent(toggleButton, 'click');  

This code creates a stream of clicks on the toggleButton, but it doesn't do anything else yet. We need to subscribe to this stream so we can perform an action with a click.

clicks.subscribe ((e) => {  
  // Handle click
});

The code we've written behaves the same as it did with addEventListener. Now we could simply toggle the visibility in the subscribe callback, but that wouldn't be ideal. After all, we want to separate the decision making from the DOM methods. I want to create a specific stream that tells the subscribers whether the toggle button is off or on. In order to do this, I'll apply a map to the clicks stream and assign it to a 'new' stream, called toggle. Every click event object will be mapped by the determinePanelVisibility method, and it will return the transformed value.

const determinePanelVisibility = () => {  
  return toggleablePanel.style.display === 'none'
}

const toggle = clicks.map(determinePanelVisibility);  

So every click event object will be transformed to a boolean, being true of false, depending on the visibility of the panel. So if we subscribe to the toggle stream, we have this boolean as the first argument, instead of a click event object.

toggle.subscribe ((show) => {  
  // argument `show` is a boolean
  toggleablePanel.style.display = show ? 'block' : 'none';
});

When we run this code, we see that the toggle button works fine. The only thing we still need to take care of is initially hiding the panel. Of course, we could add the if-statement outside our stream, just like we did in the initial code. But we already have this code in our stream, so why write it again? What we need to do is fake a click on the button, so we can start with the value of buttonInitialState.

This is when the startWith operator enters the stage. This method does exactly what the name suggests. We can fake a stream dispatch with a specific value, in our case the value of buttonInitialState.

const toggle = clicks  
                .map(determinePanelVisibility)
                .startWith(buttonInitialState);

Now the subscribers are called immediately and the panel is initially hidden. See it in action here. Awesome. In our scenario, the determinePanelVisibility method doesn't rely on the passed values, so we could also put the startWith method before the map. In most cases, you have to be careful where you place the startWith. For example, if our determinePanelVisibility method would use a property of the passed event's target for making its decision to return true or false, our code would break when we put the startWith before the map. Because buttonInitialState is a boolean and, obviously, determinePanelVisibility wouldn't know what to do with it.

Better streams: remove DOM interaction from our stream

Although the toggle button worked as intended, I wasn't happy with the result. In the determinePanelVisibility, we still use the DOM to determine the state. I searched the RxJS docs, but couldn't find a method that would work for me. So I dropped a question at StackOverflow. A guy named user3743222 mentioned the Scan method, and gave me a small snippet to work with. The Scan operator...

... applies a function to the first item emitted by the source Observable and then emits the result of that function as its own first emission. - http://reactivex.io/

Exactly what we need! The Scan operator's first argument is an accumulator function to be invoked on each element, and the second argument is the initial accumulator value (buttonInitialState in our case). Please note that this order is switched when using RxJS 2.X.

In our accumulator function, we only want to invert the passed boolean value, because that's how a toggle button works under the hood, right? Hence, we can write a really easy accumulator function. We're only interested in the first parameter, which is the accumulated value.

const toggleState = currentState => !currentState;  

We can now replace the map with our new scan. Now the code is far more reusable as the stream does no longer rely on a specific DOM element's visibility. Here's the complete example:

const toggleButton = document.querySelector('.toggleButton');  
const toggleablePanel = document.querySelector('.panel');  
const buttonInitialState = true;  
const clicks = Rx.Observable.fromEvent(toggleButton, 'click');  
const toggleState = currentState => !currentState;

const toggle = clicks  
                .scan(toggleState, buttonInitialState)
                .startWith(buttonInitialState);

toggle.subscribe ((show) => {  
  toggleablePanel.style.display = show ? 'block' : 'none';
});

See https://jsbin.com/bovewobaxu/ for a working demo.

When you have access to a functional library like Ramda, you can even omit the toggleState function, and replace it with something like `R.not', making things even more clean.

const toggle = clicks  
                .scan(R.not, buttonInitialState)
                .startWith(buttonInitialState);

See demo here.

Final thoughts

I'm pretty new to RxJS and reactive programming in general, so I can imagine a seasoned programmer could improve the implementation of the toggle button. I'm very curious, so please drop your thoughts in the comments.

Although I'm starting to wrap my head around the concept of reactive programming and I am pretty confident with functional programming in general, I still feel it can add a lot of (false) complexity to simple programs. Also, it really asks for a very functional mindset that, at least in my environment, isn't (yet) in the heads of average JavaScript developer.

Functional and reactive programming should improve readability of our programs, and I believe it will eventually, as soon as more and more people will start using it in their software. It will change the way we write our code immensely. Adding it so the ES7 specification is a great step in the right direction.