Home » AngularJS » Writing your AngularJS with Redux

Writing your AngularJS with Redux

There are good and bad things about 2 way data-binding in AngularJS. It can be very handy for small app because it’s save a lot of coding time. But on the other hand, when your app growing, it adds complexity to AngularJS digest’s cycle and hurt UI performance, it also makes your app harder to debug because your don’t know which directives make changes to your data.

Redux comes to the rescue.

It is originally made for React but many other SPA frameworks are using Redux as well. It provides an easy way to centralize the state of your application. When using Redux, your data is travel in one way. From the component, you can fire a action, your action comes to reducer. Reducer changes the state of your app and state updated to your app. Your data can no longer travel back and forth between controller and directives inside your app. By using Redux, your logic layer is decoupled from your presentation

If you want to learn more about Redux, go to http://redux.js.org/index.html. Initially, it’s hard to grasp this pattern but when you try to read the example few times you will like the idea behind Redux.

About the app in this example, it has 2 directives, when user select an item on the list directive, the detail directive will show the detail of selected item.

You can take the source code of this app at here: https://github.com/davidtran/angularjs-ngredux-candidatelist

Why AngularJS sucks in this situation ?

  • You don’t want to use $rootScope. It will pollute your $rootScope with many different even listeners. 
  • Create a service is nice but it has one problem: your application state will be scratched everywhere when you have more than one module and directives.
  • “require” the directive will be create a tight couple which is bad bad bad for your application.

Why I use Redux:

  • Redux centralize your data, when you app grow, you can still manage & debug your application data very easy. 
  • Data flow in one way only and you can debug easier.
  • When your app do something wrong, you can quickly check which action causes the issue.
  • Decouple the logic layer from the presentation layer. AngularJS only responsible for the presentation only. In later stage of your product, if you want use Ionic, you just copy the whole Redux actions and reducers which you have already created for your AngularJS to your new app and it still works perfectly. Even if you move your app to React, React Native or Backbone without rewriting the whole logic layer.

Here is the app:

We start writing the Redux actions first:

export const SET_SELECTED_CANDIDATE = 'SET_SELECTED_CANDIDATE'

export function setSelectedCandidate(candidate) {
  return {
    type: SET_SELECTED_CANDIDATE,
    candidate
  }
}

And the Redux reducer:

import { SET_SELECTED_CANDIDATE } from '../actions'

export default function reducer(state = {
  selectedCandidate: null,
  candidates: [
    {
      name: 'David Tran',
      title: 'Frontend developer',
      salary: 70000
    },
    {
      name: 'Mike Nguyen',
      title: 'Backend developer',
      salary: 60000
    },
    {
      name: 'Sascha Gros',
      title: 'Full-stack developer',
      salary: 80000
    }
  ]
}, action) {
  switch (action.type) {
    case SET_SELECTED_CANDIDATE:
      return Object.assign({}, state, {
        selectedCandidate: action.candidate
      })
    default:
      return state
  }

}

That’s the Redux part, the logic layer of our application. Now we build the presentation layer by AngularJS. First we need to bind ng-redux with our app by create a store writing in the app.

require('../styles/app.css')

import angular from 'angular'
import reducer from './reducers'
import ngRedux from 'ng-redux'
import createLogger from 'redux-logger'
import candidateDetail from './candidate-detail'
import candidateList from './candidate-list'

angular
  .module('app', [ngRedux])
  .config(($ngReduxProvider) => {
    $ngReduxProvider.createStoreWith(reducer, [createLogger()])
  })
  .controller('appCtrl', () => {})
  .directive('candidateDetail', candidateDetail)
  .directive('candidateList', candidateList)

Here is the implementation for directives. Just like what we do with Redux, we need to declare methods to bind methods and state from Redux to the directive.

candidateList directive:

import {setSelectedCandidate} from './actions'

export default function candidateList($ngRedux) {
  return {
    template: [
      '<ul>',
      '<li ng-repeat="candidate in candidates" ng-click="setSelectedCandidate(candidate)">{{ candidate.name }}</li>',
      '</ul>'
    ].join(''),
    restrict: 'E',
    link: (scope) => {
      // Connect to Redux store
      let unsubscribe = $ngRedux.connect(mapStateToScope, mapDispatchToScope)(scope)

      // Unsubscribe to changes when your directive is destroyed
      scope.$on('$destroy', unsubscribe)

      // We only get the candidate
      function mapStateToScope(state) {
        return {
          candidates: state.candidates
        }
      }

      function mapDispatchToScope(dispatch) {
        return {
          setSelectedCandidate: (candidate) => dispatch(setSelectedCandidate(candidate))
        }
      }
    }
  }
}

In the candidateList directive, we import action from Redux and when user click on an candidate item. We fire that action to set the selected candidate. That candidate will be displayed by candidateDetail directive.

Here is the implementation of candidateDetail:

export default function candidateDetail($ngRedux) {
  return {
    template: ['<div class="candidate-detail" ng-if="candidate">',
      '<div class="name">Name: {{ candidate.name }}</div>',
      '<div class="title">Title: {{ candidate.title }}</div>',
      '<div class="salary">Salary: {{ candidate.salary }}</div>',
      '</div>'].join(''),
    restrict: 'E',
    link: (scope) => {
      // Connect your directive to Redux store
      let unsubscribe = $ngRedux.connect(connectStateToScope)(scope)

      // Cleanup
      scope.$on('$destroy', unsubscribe)

      // Connect state to your scope
      function connectStateToScope(state) {
        return {
          candidate: state.selectedCandidate
        }
      }
    }
  }
}

 

And that’s how we implement to an AngularJS app using Redux library. If you haven’t used Redux, you might have a hard time to grasp the new thinking mental of Redux. But after you get along with you, you will enjoy the new experience. About me. I really like to implement my app using Redux. I found that my app is easier to refactor and maintain.