In angular, I have an object that will be exposed across my application via a service.
Some of the fields on that object are dynamic, and will be updated as normal by bindings in the controllers that use the service. But some of the fields are computed properties, that depend on the other fields, and need to be dynamically updated.
Here's a simple example (which is working on jsbin here). My service model exposes fields a
, b
and c
where c
is calculated from a + B
in calcC()
. Note, in my real application the calculations are a lot more complex, but the essence is here.
The only way I can think to get this to work, is to bind my service model to the $rootScope
, and then use $rootScope.$watch
to watch for any of the controllers changing a
or b
and when they do, recalculating c
. But that seems ugly. Is there a better way of doing this?
A second concern is performance. In my full application a
and b
are big lists of objects, which get aggregated down to c
. This means that the $rootScope.$watch
functions will be doing a lot of deep array checking, which sounds like it will hurt performance.
I have this all working with an evented approach in BackBone, which cuts down the recalculation as much as possible, but angular doesn't seem to play well with an evented approach. Any thoughts on that would be great too.
Here's the example application.
var myModule = angular.module('myModule', []); //A service providing a model available to multiple controllers myModule.factory('aModel', function($rootScope) { var myModel = { a: 10, b: 10, c: null }; //compute c from a and b calcC = function() { myModel.c = parseInt(myModel.a, 10) * parseInt(myModel.b, 10); }; $rootScope.myModel = myModel; $rootScope.$watch('myModel.a', calcC); $rootScope.$watch('myModel.b', calcC); return myModel; }); myModule.controller('oneCtrl', function($scope, aModel) { $scope.aModel = aModel; }); myModule.controller('twoCtrl', function($scope, aModel) { $scope.anotherModel = aModel; });
The main difference is the availability of the property assigned with the object. A property assigned with $scope cannot be used outside the controller in which it is defined whereas a property assigned with $rootScope can be used anywhere.
$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.
$digest(), $rootScope. $apply(). It updates the data binding. It iterates through all the watches and checks any value updated.
Although from a high level, I agree with the answer by bmleite ($rootScope exists to be used, and using $watch appears to work for your use case), I want to propose an alternative approach.
Use $rootScope.$broadcast
to push changes to a $rootScope.$on
listener, which would then recalculate your c
value.
This could either be done manually - i.e. when you would be actively changing a
or b
values, or possibly even on a short timeout to throttle the frequency of the updates. A step further from that would be to create a 'dirty' flag on your service, so that c
is only calculated when required.
Obviously such an approach means a lot more involvement in recalculation in your controllers, directives etc - but if you don't want to bind an update to every possible change of a
or b
, the issue becomes a matter of 'where to draw the line'.
I must admit, the first time I read your question and saw your example I thought to myself "this is just wrong", however, after looking into it again I realized it wasn't so bad as I thought it would be.
Let's face the facts, the $rootScope
is there to be used, if you want to share anything application-wide, that's the perfect place to put it. Of course you will need to careful, it's something that's being shared between all the scopes so you don't want to inadvertently change it. But let's face it, that's not the real problem, you already have to be careful when using nested controllers (because child scopes inherit parent scope properties) and non-isolated scope directives. The 'problem' is already there and we shouldn't use it as an excuse not follow this approach.
Using $watch
also seems to be a good idea. It's something that the framework already provides you for free and does exactly what you need. So, why reinventing the wheel? The idea is basically the same as an 'change' event approach.
On a performance level, your approach can be in fact 'heavy', however it will always depend on the frequency you update the a
and b
properties. For example, if you set a
or b
as the ng-model
of an input box (like on your jsbin example), c
will be re-calculated every time the user types something... that's clearly over-processing. If you use a soft approach and update a
and/or b
solely when necessary, then you shouldn't have performance problems. It would be the same as re-calculate c
using 'change' events or a setter&getter approach. However, if you really need to re-calculate c
on real-time (i.e: while the user is typing) the performance problem will always be there and is not the fact that you are using $rootScope
or $watch
that will help improve it.
Resuming, in my opinion, your approach is not bad (at all!), just be careful with the $rootScope
properties and avoid ´real-time´ processing.
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