Is it possible to implement a Redux-like architecture in Angular 1 using $rootScope as the store?
Asked Answered
M

1

5

If you're in a big legacy Angular 1 codebase and you don't want to introduce new dependencies (like ngRedux), would it be a terrible idea to start using classic Angular 1 features, such as $rootScope, $broadcast, $on, $watch to implement a Redux-like architecture?

The way I see it, it could be done as following:

  • For store/model -> use $rootScope
  • For store.dispatch(ACTION) -> use $rootScope.$broadcast(ACTION)
  • Reducers would be implemented as services injecting $rootScope and doing $on(ACTION)
  • Controllers could watch for changes on $rootScope with $watch and update the view or views could bind directly to $rootScope properties

As long as you're disciplined to not do weird out-of-place mutations on $rootScope properties, keep all application logic in the Reducers and keep controllers code to a minimum, the greatest drawback I can see with this is having terrible performance due to Angular 1 expensive digest cycles. But if you can also stick to immutable data structures, it might not even be the case.

Is this a bad idea? Has anybody tried this?

Miniskirt answered 11/4, 2016 at 13:40 Comment(0)
P
7

Of course, you can do your thing using only $rootScope. But is has a lot of other purposes that will interfere with your state.

Let's see:

// reducer-like
$rootScope.$on('COUNTER_INCREMENT', (e, action) => {
   //I don't use += to emphase immutability of count property;
   $rootScope.count = $rootScope.count + action.value;
});

//action-ish creator
$rootScope.$broadcast('COUNTER_INCREMENT', {value: 5});

//store subscribe-like
$rootScope.$watch('count', (count) => {
  //update something in your component
})

You can do it if you want, but you see there an issue with unclear immutability. It is very hard to control that your action handler is pure, because it actually is not.

There is no default action handler, you can't set an initial state of store easy. And you still use $watch and digests, that you probably want to avoid.

Also, you can't subscribe to all updates, there is no a single point of reset everything like Redux does it with time-traveling things.

If you want to keep using Redux as a pattern you will probably end with some helpers that will make your code more Redux-like.

Well, why not just start using Redux from a simple angular utility service:

angular.module('app', []).factory('store', () => {
  //if you have many reducers, they should be as separate modules, and imported here
  function counter(state = 0, action) {
    switch (action.type) {
      case 'INCREMENT':
        return state + action.value;
      default:
        return state;
    }
  }

  // here you can add here middlewares that make Redux so powerful
  return Redux.createStore(counter);
});

Here we go. Now you can use your store service for actions

angular.module('app').controller('MyController', ($scope, store) => {
  store.subscribe(() => {
    //you may not to care about $scope.$digest, if your action was triggered within angular scope as well 
    $scope.count = store.getState();
  });

  $scope.onClick = () => store.dispatch({type: 'INCREMENT', value: 1});
});

Now you have a proper setup, where your subscribers don't know about action that was caused dispatch, your action creation and action reducing logic completely independent as it required by Redux pattern.

Phonotypy answered 11/4, 2016 at 21:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.