← home

Emflux - a Flux Library for Ember

October 27th 2015

As my Ember chat started to grow, I became more and more unsatisfied about how and where the application state was mutated inside the app. Changes happened not just in different levels on the component hierarchy but also inside the controllers. Additionally, there were chains of mutations that were hard to debug.

For a long time I felt that for an app that doesn't use Ember data, a systematic pattern to follow was missing. Ember 2.0 recommends the data down actions up approach. I initially tried to implement that the best I could but found two issues.

First is that there will be a lot of action parameters and handlers to maintain. Components become harder to move around if the actions are hardcoded in the parent component. Closure Actions introduced in the Ember v1.13 release helped a lot but won't completely solve the problem.

Secondly, if you take the guidance to the extreme (to me everything else felt arbitrary) you will get a huge root component, which handles all mutations. That component ends up to be very special and doesn't scale well. In my chat app case, it still wasn't the only place where mutations took place, as an another utility object received the updates from the server.

At some point I got interested in knowing how React handles this problem and found the Facebook Flux library. It made a lot of sense to me. I was particularly happy to see that Flux is advertised more as a data flow pattern than a specific library. Pretty soon I wanted to try to build an Ember adaptation of it.

The result is now finally available as ember-cli-emflux. It is a small Ember add-on that tries to follow the flux pattern as closely as possible by providing an extensible store class, a dispatcher object, and a stores service.

A simple store could look like:

import Ember from 'ember';
import fetch from 'fetch';
import Store from 'emflux/store';
import Post from '../models/post';

export default Store.extend({
  currentPost: 1,
  posts: Ember.A([]),

  handleCreatePost(params) {
    // [ ... Post to server, get an ID ... ]    
    this.get('posts').push(Post.create({ body: body, id: response.id }))
  },

  handleDeletePost(params) {
    // [ ... Delete from server ... ]
    let post = this.get('posts').findBy('id', params.id);
    this.get('posts').removeObject(post);
  },

  handleSetCurrentPost(params) {
    // [ ... Get from server if missing from store ... ]
    this.set('currentPost', params.id);
  }

When this file is saved as app/stores/posts.js, ember-cli-emflux will pick it up automatically and instantiate it as a singleton post store object.

Post store here exposes the currentPost integer and posts array. It is also ready to handle CREATE_POST, DELETE_POST, and SET_CURRENT_POST events. In a real app, these handlers could contact the server first before making the changes. Post model is simply an Ember object that exposes some properties and optional computed convenience properties.

With this store in place, components can have read access to the exposed store data. Components can also trigger store events with dispatch() method. Like this:

import Ember from 'ember';
import { dispatch } from 'emflux/dispatcher';

export default Ember.Component.extend({
  stores: Ember.inject.service(),

  posts: Ember.computed.oneWay('stores.posts.posts'),

  newPost: '',

  actions: {
    newTodoPost() {
      dispatch('CREATE_POST', { body: this.get('newPost') });
      this.set('newPost', '');
    }
  }
});

Stores service is provided by the emflux library. With it components can read store variables. In this case posts array is now ready for the template to use. When a component wants to initiate an app state change, it calls dispatch(). These should be the only two ways the component interfaces with the outside world.

Whether the component is a root component or buried deep in the component tree, it shouldn't make any difference. All of them can read store data and dispatch events directly without going through their parent.

The component doesn't need to know which store or stores handle the event. The Emflux dispatcher will execute matching handlers from all stores. If necessary, handlers itself can communicate with the server using any suitable library directly, like jQuery.ajax(), Socket.IO, or ember-fetch, removing the need for adapters.

In the above example, the route could set the currentPost variable by dispatching SET_CURRENT_POST event. In fact, any part of the app can dispatch events without eroding the app architecture. A store still controls its domain in the app.

Socket.io works especially nicely with flux, as server generated events can be dispatched as emflux events with a minimal conversion. Also, other flux benefits are available, like dispatcher being a single tracing point that can store a replayable log of events that lead to a specific error situation.

Emflux is still a basic and experimental library. But I have used it in my chat app now couple weeks succesfully.

It doesn't actually enforce read-only access to store data. I'm not sure if there is a simple way to implement that. Currently it also skips the Action Creator module between the components and dispatcher that is part of the Facebook Flux library. Action creators could handle server interactions which would make Emflux stores completely synchronous.

I'd be happy to hear any feedback or improvement ideas you might have! The Emflux README file contains some additional information about the library features.





comments powered by Disqus