Emflux - a Flux Library for Ember
October 27th 2015As 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