A Dead-Simple Todo List with RxJS
18 Sep 2015I’ve recently been playing around with RxJS. If you’re not familiar with RxJS, I would suggest watching this talk by Jafar Husain about how Netflix used RxJS to build it’s new front-end.
I’m really interested in trying to wrap my head around RxJS, and functional programming in general, just because it’s so different than what I’m used to doing. I’ve read a ton of articles, and pored through the RxJS docs, but I had a really hard time implementing anything more than a simple snippet.
There is a TodoMVC example using RxJS, and I spent quite a bit of time trying to grok what was going on in there. The example Todo app just tries too hard to show off all the features, which makes it really tough to understand for a beginner.
After a many meeting between my head and the desk, I finally came up with a simple list view using RxJS and Cycle.js.
So in the interest of saving the brain cells of other developers, I present to you:
A Dead-Simple Todo List with RxJS
What is RxJS?
In short, RxJS is a library for working with asynchronous streams, which it calls “Observables”. Let’s, for example, take a look at a stream of click events in RxJS.
NOTE: This is a live example using JS Bin. Click the ‘Output’ tab to test out the application.
What we’ve written is essentially an event-handler for a button click event. But the code looks a lot more like data-processing logic than event-handling logic. In fact, it can be quite helpful to think about RxJS observables as asynchronous arrays.
Cool, but how do you code an actual application?
I’m glad you asked, because that happens to be the topic of this very blog post!
Let’s start by taking a look at Cycle.js, a small application component for RxJS. Cycle.js will render a virtual-dom from a stream of application states.
Let’s try integrating Cycle.js with our button-click example.
As you can see, the core business logic is similar to our first example. The main differences are in:
- How we listen to user intentions (
DOM.get()
, instead ofRx.Observable.fromEvent()
) - How we send back views to the user (virtual-dom stream using
h()
, instead of direct manipulation with jQuery)
Let’s refactor
That main()
function is a little long, and I’m seeing view stuff right next to state stuff, which I don’t really like. The cycle.js docs propose a Model-View-Intent pattern. Without going to much into it, I’ll show you how that might look with this code.
Ah… much better, dontchya think?
The Todo List
The TodoMVC spec requires a lot of things. But because we’re making a dead simple todo list, I’m only going to require two things:
- A user can add an item to their todo list, by typing text into a textbox
- A user can remove an item from their list, by clicking a delete button next to the item.
We’ll also make the app ugly semantic, so we don’t have to worry about CSS or strange markup.
Requirement 1: A user can add an item to their todo list
If you look at our even-numbers button example from earlier, you’ll see we already have the bones for a user to add something to a list view. Let’s see if we can just replace those buttons with a text input.
Pretty good, eh?
Requirement 2: A user can remove an item from their list
This is where things get a little tricky. So far, we’ve just been taking a steam of inputs and adding each item in the stream to a list view. We could visualize that like so
The problem is that now we want to remove an item from the list view, and there’s no way to go back and remove an item from the stream.
So instead of thinking about a stream of list items, let’s try thinking about a stream of operations on state. What do I mean by that? Well, let’s start by looking at an addTodo
operation:
As you can see, the addOperation
returns a function which receives a state, and sends back a modified state with the new todo item:
And we could easily do this same thing for a removeTodo
operation
Makes sense? Yes? Good.
A stream of operations
So now, instead of thinking about working a stream of todo items, let’s think about working with a stream of operations on our state:
We’ll implement this pattern by mapping our intents
to operations, and then applying each operation to the state using scan()
:
Here’s the whole thing, in all it’s dead-simple glory.
Let’s review what we did here:
- We created stream of user-initiated events (input todo item text, click a delete btn)
- We mapped those events to named “intentions”, giving them meaning specific to our application
- We mapped our intentions to operations on a state, giving them a consistent interface
- We applied each operation to the state
- We mapped the state to a
virtual-dom
, which was renderd by Cycle.js
I feel like this is a fairly elegant approach to building applications, and I hope this helps you get started with RxJS and Cycle.js.
If you want to play around with the Todo list app, you can download my repo on github. You can also see how I pulled out the item
logic into a custom element.
I also put together a canvas pong game using Cycle.js with a custom Canvas
driver. It’s a bit of a work-in-progress at the moment, but you’ll get an idea of how you can build a more complex application with RxJS.
Happy coding!