Angular2 is getting into the whole Redux thing with ngrx/store and ng-redux

by
Tags: , , , , ,

Well, it was a matter of time… The React world has been going crazy over Flux, Reflux, Redux, and acid reflux (tm?)

On the Angular 2 front, we’ve been diving into reactive programming with the ReactiveX RxJs project, which gives Angular stream processing via Observables. Couple that with the fact that Angular 2 is a comprehensive framework, and things move a bit slower around here.

But that’s about to change, at least a bit. There are several projects that have been bringing a reducer-based state store to Angular, namely @ngrx/store by Rob Wormald, and angular-redux/ng2-redux. Both teams are developing APIs in the style of Redux, which is a functional-programming-driven immutable state store, using reducer functions and a dispatching mechanism to handle changes in state.

Wait, what?

At its heart, Redux-style programming follows these tenets:

  • All state is stored in a single state store
  • Requests to change state have to go through a dispatcher
  • You send an action to the store
  • Functions, called reducers, get a chance to operate on the action
  • If a reducer executes code based on an action, it is required to replace the state completely with a new immutable value
  • The store provides a stream of updates that can be watched by components needing to bind that data to the page or other components

Some of you may be thinking “Dispatcher? Struts?” and you are kind of correct, in that a coming anti pattern will likely be thousands of reducer functions in a big file or smearing reducers all across your application code base. But that’s hopefully not how this will be done in practice.

A practical example using ngrx/store

We’ll be reviewing some code from Rob Wormald’s plunk sample, so feel free to launch the link and follow along.

At the heart of the API is the store. This object contains the state of your application, and is wired up using Angular’s provide method. In src/main.ts we see (I’m removing some extra stuff):

bootstrap(App, [
  provide(Store,
    {useValue:
      combineReducers({todos, visibilityFilter}) })
])

The Store class represents the API of the store system. The combineReducers call takes reducer functions, and registers them for processing any requests that come along from calling clients.

A reducer

You don’t need to understand the whole solution to see the pattern, just that th sample is a simple todo list. Let’s look at how a todo might be processed via the reducer for todos. First, the skeleton of the reducer:

export const ADD_TODO = 'ADD_TODO';
export const UPDATE_TODO = 'UPDATE_TODO';
export const DELETE_TODO = 'DELETE_TODO';
export const COMPLETE_TODO = 'COMPLETE_TODO';
export const todos = (state = [], {type, payload}) => {
  console.log('ACTION:', type, payload);
  switch(type){
    case ADD_TODO:
     ..
    case UPDATE_TODO:
     ..
    case COMPLETE_TODO:
     ..
    case DELETE_TODO:
     ..
    default:
      return state;
  }
}

Ok, so we can see a pattern here – each action executes some code that manipulates the state in some way. This todos function is a reducer, and reducers are supposed to process data and reduce it down to a final value. That’s the general pattern, and in the @ngrx/store project, that means it sets a new immutable object for state. In other words, state changes are made by the store and the store only by processing reducers. This makes the state a single point of change, and lets you test your data transitions without the UI.

Let’s look at how Rob adds a new todo:

   return state.concat(
      [Object.assign({}, payload, {id: state.length + 1})]);

So Rob used the concat method of the array to make a new array, based on the old one plus a new entry.

If you look down the rest of the reducer, it’s much the same. Bottom line, the state changes through the reducer function based on the action being passed. If no action works for the reducer, it just reflects the state as it is, doing no harm.

Using a store

The sample is very elegant, using a <todo-list> and <todo-list-item> component to render the list and each item, but they are all fed via an observable, fetched via this call in the app.ts constructor:

  constructor(private store:Store) {
    this.todos = store.select('todos')
      .combineLatest(
        store.select('visibilityFilter'),
                     (todos, visibilityFilter) => {
        return todos.filter(visibilityFilter)
      });
  }

In short, he injects the store, then requests it to bring us back a stream of todos via store.select('todos'). The combineLatest call takes the most recent values out of the store and joins it to the updates to the visibilityFilter. If either one changes, it calls the filter method, applying the updated filter (which is the job of the filter reducer).

One thing that is not clear is how the typing is figured out for the todos collection. It looks like he’s leaving the typing up to the browser. My educated guess is that the type is Observable(any[]).

Providing another reducer for filtering

Now, you’re probably wondering how the filter works. It’s just reducing an expression (value or function) from the potential states of the filter, which are ALL, COMPLETE and PENDING. This reducer is easy to figure out, if you assume it’s just the predicate of the actual filter function applied whenever you change the filter setting:

export const ALL = 'ALL'
export const COMPLETE = 'COMPLETE'
export const PENDING = 'PENDING'
export const visibilityFilter =
  (state = (todo) => true, {type, payload}) => {
  switch(type){
    case ALL:
      return (todo) => true;
    case COMPLETE:
      return (todo) => todo.completed;
    case PENDING:
      return (todo) => !todo.completed;
    default:
      return state;
  }
}

Now, that helps to inform how he applies the filter in the constructor. Whenever the visibilityFilter value changes, it triggers an update to the observable, which he then uses to call the filter function on the array:

....
return todos.filter(visibilityFilter);

Other APIs

Since others use Redux programming APIs there are a few out there. I tweeted my interest in ngrx/store at ng-conf and the author of ng2-redux gave me a shout. It has similar goals so check that project out. Also, if you’re on Angular 1 you should give the ng-redux project a spin as well.