Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 687
Dema Avatar asked Apr 11 '16 13:04

Dema


People also ask

What is $rootScope in Angular?

All applications have a $rootScope which is the scope created on the HTML element that contains the ng-app directive. The rootScope is available in the entire application. If a variable has the same name in both the current scope and in the rootScope, the application uses the one in the current scope.

How many $rootScope an Angularjs application can have?

An app can have only one $rootScope which will be shared among all the components of an app.

Is Redux necessary for Angular?

Remember that Redux was an answer to React problems. And just like Angular doesn't need Axios, it doesn't need Redux...

When would you use a $rootScope?

$rootScope exists, but it can be used for evilScopes in Angular form a hierarchy, prototypally inheriting from a root scope at the top of the tree. Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.


1 Answers

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.

like image 88
just-boris Avatar answered Oct 27 '22 11:10

just-boris