Caffeinated Simpleton

Om and Flux (An Ongoing Experiment)

Facebook has recently been talking a little bit about Flux, their architecture for building client-side web applications. Here I talk a little about adapting that architecture to a client-side app built with Om.


Flux at its core is a pretty simple architecture designed to minimize side effects throughout state transitions. The architecture is as follows (borrowed from the react docs)

Views ---> (actions) ----> Dispatcher ---> (registered callback) ---> Stores -------+
Ʌ                                                                                   |
|                                                                                   V
+-- (Controller-Views "change" event handlers) ---- (Stores emit "change" events) --+

The execution flow goes something like this:

Boom. Simple.

Translating this to Om

Om does not add a whole lot on top of React, but the biggest difference complements Flux nicely.

Instead of passing state around to different components, in Om the entire application state is maintained globally. In order to give a subset of the state to a component to render, you create a cursor. This can be thought of as a view into the global state.

For instance, if your state is a list of flights, you could create two components to handle this. The FlightList component, responsible for listing the flights, and the Flight component, responsible for listing a particular flight. The FlightList component is given a cursor that points to the entire list, but an instance of the Flight component is given a cursor that only points to the particular flight that the instance is rendering.

There are some nice properties of this that we gain due to all data structures being immutable in ClojureScript, but for the purposes of this blog post, the important one is that transact! calls are synchronized per render cycle. If you change state while rendering, it will not appear until the next frame. The global state while rendering cannot be changed.

This method of handling the global state can replace Flux’s concept of stores. Instead of having many different objects responsible for maintaining many different pieces of application state, we have one global state and different objects can react to changes in it to do whatever they need to do.

As an example, instead of having a TODO store, we have views that observe the TODO state in the global application state and update as needed.

Doing things outside of views

There are many things that happens outside of user interactions in a real world web app. Actions come from more than just the user sitting in front of their computer screen (IE, when they receive a message) and more than just the UI needs to be made aware of when state changes (IE, we need to sync state with the server). So how do we manage that?

In Flux, you have bits of code that register with the dispatcher to listen for particular events and react to them. The code gets called for every event and is responsible for filtering out the events it doesn’t care about.

We can do something similar in ClojureScript with core.async.

As an example, I’ve replicated part of the Flux dispatcher API below.

(ns my.dispatcher (:require [cljs.core.async :as async :refer [chan put! pub sub unsub]])) (let [dispatch-chan (chan) dispatch-pub (pub dispatch-chan (fn [[tag & _]] tag))] (defn register [tag] (sub dispatch-pub tag (chan))) (defn unregister [tag chan] (unsub dispatch-pub tag chan)) (defn dispatch! [tag & args] (apply put! dispatch-chan tag args)))

This creates a publish/subscribe system with a lot of similarities to Flux’s dispatcher.

In order to notify the world that some action has been taken, you call dispatch.

;; This code cares about TODOs that got deleted (let [delete-chan (register :deleted-todo)] (go-loop [] (let [todo-id (That will put the todo-id onto the dispatch channel (which is internal to the dispatcher), which will in turn publish the id to every bit of code that has registered.

Example: Syncing with the server

To do something similar to what om-sync does to synchronize all application changes with a server, you can publish all state changes to the dispatcher.

To do so, we create an API service that just publishes all state changes to an endpoint.

NOTE: You could also use EDN or JSON as the payload instead of form encoding arguments if you wish. cljs-http supports all three.

(ns my.api (:require-macros [cljs.core.async.macros :refer [go-loop]]) (:require [cljs.core.async :as async :refer [Then when you’re rendering your Om app, you would do something like this.

(defn render-poll [] (om/root app-view app-state {:target (. js/document (getElementById "app")) :tx-listen (fn [tx-data root-cursor] (dispatch! [:txs tx-data root-cursor]))}))

As a simpler example, this will just print all state changes to the console.

(let [tx-chan (register :txs)] (go-loop [] (let [tx (js tx-data))) (recur)))

Pretty nifty.

The biggest difference between this approach and just publishing to some shared channel is that with the tag argument to dispatch, we can publish all sorts of actions to various parts of the application, not just state changes.


To summarize, I believe Flux can be easily emulated in Om with a few simple steps.


I’ve only just started playing with this, so I don’t know what the pros and cons are. Additional feedback is quite welcome, and I fully plan on expanding this post as I learn more about how this all works.

comments powered by Disqus