Unidirectional data flow (or sometimes referred to as reactive programming or functional programming) is often seen as this new trend in frontend applications development brought by React and Flux (or by ReactiveX from Netflix). So many developers think using one of these libraries or frameworks is necessary to start with this pattern. This, of course, is not true. I can use React and don’t have an unidirectional data flow at all, and the opposite, I can use stacks not designed with functional reactive programming in mind and still have an unidirectional data flow.

So to better understand this pattern and see how It can drastically improve code maintainability (I often read, applications developed using unidirectional data flow are easier to reason about), I’m gonna start by writing a simple AngularJS (which is not what we can call a reactive based framework) application, and see how I can rewrite it to use this pattern and how this can lead to better code quality and maintainability.

Our application

To be “uncommon” :P, I will create a typical todo application. I just took the example in the homepage of the AngularJS website and I rewrote it to make it look more like a “modern” AngularJS application (close to what could be a complex AngularJS application): A bunch of components (directives in AngularJS) not related to each other manipulating some common data (here todos). So basically I have:

  • A service todoRepository that simulate a typical backend API: findAll, archive, create and update methods returning promises and the data returned from the service and each time I call an api, a new object is returned (new instance, simulated using the angular.copy). (don’t take too much attention about the code of this service)
  • A component todos: A list of unarchived todos
  • A component remaining-todos: It displays the remaining todos count and a link to archive the todos.
  • A component todo-create: A form to create new todos.
  • All these components rely on an the todoRepository to get data from the backend.

A working example can be found in this plunkr and here is the gist with the code https://gist.github.com/youknowriad/ef57f1abbf98fb7ccf6d.js

As you may have noticed, it is not working (or at least It seems). If I had a real API, I would have seen that everything is working and persisted in the backend, but the problem here is that when I create a new todo from the todo-create component, the changes are not reflected in the two other components. The same problem happens if I click on the archive link or when I check/uncheck a todo. Each time a change is made to our backend data (the todos property of the service `todoRepository` in the example), the current component is updated correctly but the change is not reflected in the other parts of the application.

1st rewrite: adding a store

To deal with this problem and “synchronize” our data on our different components, there are multiple options:

1- I can use events may be. AngularJS allow communication between components using events. Each time a component make a change that needs to be reflected, it triggers an event and the other components could subscribe to this event to reflect the change. This seems simple at first, but could quickly lead to some spaghetti code.

2- I could take advantage of AngularJS bindings and use the same instance of todos object. Whatever I change something in the array, It will be reflected elsewhere. Well… this could work.

What I am doing here is using the same object everywhere, we could call this array a store (here I am flux :)) of data (state). The problem with this solution is that the store itself is the state of the application. It is easy to break and have components not updating because I accidentally changed the instance of the object. I also rely on the dirty-checking of the framework to reflect the changes to the views (Performance will be a big problem => mutable data). So this solution is a good starting point but it’s not very wise.

Dirty-checking is a technique used by frontend frameworks to update the view displayed. Example: For this expression {{ myvar }}, AngularJS will compare the old value of the variable to its current value, If there’s a difference, the view should be updated. This will be done in each digest cycle and for all the expressions of the application. This could quickly lead to poor performance. Dirty-checking needs to be as efficient as possible.

To understand why dirty checking for immutable data is way more performant than mutable data, let’s see how this check is performed: A naive approach for mutable (objects which attributes could be changed) would be to compute a string representation using JSON.stringify and compare the result with the old computed string representation. While for immutable data, we could only check if it’s the same object instance oldObj !== newObj. The difference in performance is obvious here.

3- What if I keep the store thing, something unique I could get the data (state) from, but instead having the same data modified (mutated) over and over again, I use a mechanism to get the update data each time there’s a change to reflect. Let’s use observables (RxJS isn’t it ? learn more about observables in this excellent talk by Jafar Husain from Netflix) here. So basically, our store could hold an observable: an object with a subscribe method and each time there’s a new change in the data (state), the callback of the subscribe method is called for each subscriber. Here is what we get using this approach:

A working example of this second iteration can be found in this plunkr and here is the gist with the code https://gist.github.com/youknowriad/0505899e278a6bebe42e.js

You will notice that updates from a component are now reflected in the other components instantly.

What I did here was adding a store service with an observable property. For simplicity and for the “academical” purpose of the post, I’ve used a simple EventEmitter for this property (You can use, RxJS, Bacon.Js or whatever observable implementation you like). I also have some methods on the store to update its state.

// store.js
function TodoStore() {
  // List of todos
  this.state = [];
  this.observable = new EventEmitter();
}

TodoStore.prototype.getState = function() {
  return this.state;
}

TodoStore.prototype.addTodo = function(todo) {
  this.state.push(todo);
  this.observable.emitEvent([this.state]);
}

TodoStore.prototype.updateTodo = function(todo) {
  var index = this.state.findIndex(function(elt) { return elt.id === todo.id });
  if (index !== -1) {
    this.state[index] = todo;
    this.observable.emitEvent([this.state]);
  }
}

TodoStore.prototype.loadTodos = function(todos) {
  this.state = todos;
  this.observable.emitEvent('update', [this.state]);
}

angular.module('todoApp').service('todoStore', TodoStore);

The components that needs the data (state) of the store, can get the initial value, using the getState method of the service and in the meantime, subscribe to the observable to get updates as they arrive to the store.

// component.js
function TodoStore() {
  // List of todos
  this.state = [];
  this.observable = new EventEmitter();
}

TodoStore.prototype.getState = function() {
  return this.state;
}

TodoStore.prototype.addTodo = function(todo) {
  this.state.push(todo);
  this.observable.emitEvent([this.state]);
}

TodoStore.prototype.updateTodo = function(todo) {
  var index = this.state.findIndex(function(elt) { return elt.id === todo.id });
  if (index !== -1) {
    this.state[index] = todo;
    this.observable.emitEvent([this.state]);
  }
}

TodoStore.prototype.loadTodos = function(todos) {
  this.state = todos;
  this.observable.emitEvent('update', [this.state]);
}

angular.module('todoApp').service('todoStore', TodoStore);

The last change I did was calling methods on the store after each successful API call that change the data. (I launch actions on the store to update it’s state).

// service.js
angular.module.factory('todoRepository', fuction(todoStore, $http) {
  return {
    // ...
    loadAll: function() {
      return $http.get('/todos').then(function(todos) {
        todoStore.loadTodos(todos);
      })
    }
    
    // Samething fore all api calls
  };
});

Did you notice how the data flow seem to have an unidirectional data ?

  • User triggers an event on a component
  • The components trigger an API call
  • API calls trigger actions on the store
  • The store provides the components with the new state
  • Restart again

2nd rewrite: immutable state, dispatcher and action creators

The application start looking good, but you know what, there’s still some issues and I can do better:

  • There’s still some mutable data in the store (It can have impacts on dirty-checking)
  • The API service is coupled to the store

Now, take a look at this third version https://gist.github.com/youknowriad/dfd8ab10abf6fcfec6d2.js and here is the plnkr

The changes I made for this version are:

  • The store is also an action dispatcher: I no longer update the state by calling store methods, but the unique way of updating it is calling a dispatch method with an action as parameter. The action is simple javascript object describing the change to apply.
  • The state of the store is immutable, each time a new action is dispatched, a new state is created.
  • I also added a reducer: A pure function to create a new state from the old state when an action is dispatched to the store.

“A pure function is a function where the return value is only determined by its input values, without observable side effects.” Wikipedia

// reducer.js
function todoReducer(state, action) {
  switch (action.type) {
    case 'add-todo':
      var newState = state.slice();
      newState.push(angular.copy(action.todo));
      return newState;
    case 'update-todo':
      return state.map(function(todo) {
        if (todo.id === action.todo.id) {
          return angular.copy(action.todo);
        } else {
          return todo;
        }
      });
    case 'load-todos':
      return action.todos.slice();
    default:
      return state;
  }
}
// store.js
function TodoStore() {
  // List of todos
  this.state = [];
  this.observable = new EventEmitter();
}

TodoStore.prototype.getState = function() {
  return this.state;
}

TodoStore.prototype.dispatch = function(action) {
  this.state = todoReducer(this.state, action);
  this.observable.emitEvent('update', [this.state]);
}

angular.module('todoApp').service('todoStore', TodoStore);
  • The API service is no longer coupled to the store, It just do (simulate) simple http requests, nothing more.
  • The components don’t call APIs directly but trigger (bound) actions creators. The role of action creators is to create actions’ objects from some user input. They can be bound, it means that they call the store dispatch method after creating the actions.
// action-creators.js
function TodoActions(todoStore, todoRepository) {
  this.store = todoStore;
  this.repository = todoRepository;
}

// Action creator
TodoActions.prototype.addTodo = function(todo) {
  return this.repository.create(todo).then(function(createdTodo) {
    // The object passed to the dispatch is an action
    this.store.dispatch({
      type: 'add-todo',
      todo: createdTodo
    });
  }.bind(this));
}

// ....

angular.module('todoApp').service('todoActions', TodoActions);

// component.js
angular.directive('mydirective', function() {
  return {
    // ...
    controller: function($scope, todoActions) {
      
      this.create = function(todo) {
        todoActions.addTodo(todo);
      }
      // ...
    }
  }
});

That’s it, now we have an AngularJS application written using a reactive approach, easy to maintain and extend. For each feature to implement, we repeat the same thing over and over again:

  1. Adding data to the store’s state
  2. Adding the reducer responsible of updating this data, using actions: (state, action) => state
  3. Creating components that subscribe to the store to get the data and display it.
  4. Calling actions creators from the components to react to user input.

Go further

Handling more data than simple todos

There’s two different solutions when dealing with complex data:

1- The classical flux approach: We use a single dispatcher as an event emitter, but for each “kind” of data, we have a different store that subscribes to the dispatcher and handles the actions to alter its proper state. The components then subscribe to whatever store they need.

2- The “redux” approach: I see this as a simplified and nicer Flux. In redux, there is only one global store for the entire application. It’s state can be updated with one global reducer. This reducer is a combinaison of smaller reducers, each one update a small part of the state. The components subscribe to the whole store, but can use “selectors” (see reselect) which is a nice way to extract only the desired data from the global state.

Using some good libraries

The code I wrote is not perfect. In a real world example, I would use redux for the store/dispatcher part. I could use a redux middleware that enables dispatching promises of actions, instead of simple actions. (Less coupling, code more testable). You can check the excellent redux documentation to learn more about middlewares.

Conclusion

If you were brave enough to read until here, I hope you now have a good understanding of all these concepts behind the reactive programming approach and how they can improve your frontend development workflow. And may be you can click the small “heart” right down here :).

Edit

Some of you asked for a last version using redux : here it is the gist and the plnkr

The changes from the previous version are :

  • The store is a redux store using a promise middleware (I can dispatch promises that return actions)
  • My action creators are not coupled to the store, they are just dumb functions that return promises of actions.

One thought on “ Understand unidirectional data flow by practice: rewrite an AngularJS application ”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s