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:
$rootScope
store.dispatch(ACTION)
-> use $rootScope.$broadcast(ACTION)
$rootScope
and doing $on(ACTION)
$rootScope
with $watch
and update the view or views could bind directly to $rootScope
propertiesAs 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?
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.
An app can have only one $rootScope which will be shared among all the components of an app.
Remember that Redux was an answer to React problems. And just like Angular doesn't need Axios, it doesn't need Redux...
$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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With