Reputation: 1956
I have a structure like this (much simplified):
<div ng-controller="MainCtrl">
<div>Views and other stuff</div>
<div ng-controller="MyCtrl">
<div ng-repeat="item in $root.items track by $index">
{{item.id}}
</div>
<div>
Total value: {{totVal}}
</div>
</div>
</div>
And in my main controller I have defined $rootScope.items = [{id: 1,..},{id: 2,..},{id: 3,..},..] and in my other controller I have defined $scope.totVal = 0 and a watcher for the items array which updated the totVal when the items array changes;
.controller('MainCtrl', ['$rootScope', '$scope', function($rootScope, $scope) {
// Are actually fetched from a service
$rootScope.items = [
{id: 1,..},
{id: 2,..},
..
];
}])
.controller('MyCtrl', ['$rootScope', '$scope', function($rootScope, $scope) {
$rootScope.updateTotVal = function() {
var totVal = 0;
// calculations are done here
// Is correct here when I console log after all calculations are complete
console.log(totVal);
$scope.totVal = totVal;
}
$scope.totVal = 0;
$rootScope.$watch('items', function() {
$rootScope.updateTotVal();
}, true);
}]);
The problem I have is that the totVal does not update in the view. When I console log the value it displays correctly in the console but it only updates in the view if I scroll the totVal div out of view and back again. Seems like an AngularJS bug in Chrome, but can't find any similar cases when i Google. It works fine in FireFox.
If I move the totVal before the ng-repeat it works and if I remove the ng-repeat it works (I rather don't change the structure of the html since it will be much work and make the design worse). I have also tried to move the totVal to the root scope without any success.
It's only the painting/rendering that fails, Batarang and console.log always gives me the updated value.
Has anyone had this problem before?
Upvotes: 0
Views: 900
Reputation: 1956
The issue is with Angular and Chrome. If I removed an ng-repeat that was before the totVal output it worked. It also worked if I put totVal higher up in the DOM structure or before the ng-repeat. I think it has to do with the number of bindings and the complexity of the DOM, many nested DIV's breaks the binding somehow. I also noted that if I had many nested bootstrap classes like 'row' and 'col-xs-12' it also broke the binding. So it seems to be related to the browser rendering/painting the view (since scrolling the totVal out and back in to the view updated the value).
The main thing I learnt from this was to use smarter directives and try to simplify the DOM structure, especially if you use bootstrap.
Original:
<div ng-controller="MainCtrl">
<div>Views and other stuff</div>
<div ng-controller="MyCtrl">
<div ng-repeat="item in $root.items track by $index">
{{item.id}}
</div>
<div>
Total value: {{totVal}}
</div>
</div>
</div>
Different changes that made it work properly:
<div ng-controller="MainCtrl">
<div>Views and other stuff</div>
<div ng-controller="MyCtrl">
<div>
Total value: {{totVal}}
</div>
</div>
</div>
<div ng-controller="MainCtrl">
<div>Views and other stuff</div>
<div ng-controller="MyCtrl">
<div ng-repeat="item in $root.items track by $index">
{{item.id}}
</div>
</div>
<div>
Total value: {{totVal}}
</div>
</div>
<div ng-controller="MainCtrl">
<div>Views and other stuff</div>
<div ng-controller="MyCtrl">
<div>
Total value: {{totVal}}
</div>
<div ng-repeat="item in $root.items track by $index">
{{item.id}}
</div>
</div>
</div>
Upvotes: 0
Reputation: 1676
I don't know if this is exactly what you where looking for because I don't know your use case. However, I achieved a working example, see it here codepen.
I refactored your code a bit, removing the extra controller and encapsulated totVal
in a service. Also, I use the controllerAs
syntax to make everything clearer.
The problem with your code is the fact that totVal
is a primitive. Thus, when you assign it a new value, angular will not be able to update the reference. This is why you should always have a dot in your model!
Take a look at my code, you will se that I declared totVal
as var totVal = {val : 0}
. I simply update val
, the reference is kept and the view is correctly updated.
.service('totVal', function(){
var totVal = {val:0};
this.computeTotVal = function(){
//perform computations, I will just increment
totVal.val++;
}
this.getTotVal = function() { return totVal; }
})
See also this article.
Upvotes: 1
Reputation: 3003
I'm not sure if this is the culprit, but you definitely shouldn't use $rootScope
for this.
As the doc states:
$rootScope exists, but it can be used for evil
See here: https://docs.angularjs.org/misc/faq#-rootscope-exists-but-it-can-be-used-for-evil
I would rewrite your code without it, when not necessary:
<div ng-controller="MainCtrl">
<div>Views and other stuff</div>
<div ng-controller="MyCtrl">
<!-- You also had an $root.items that didn't match any element here -->
<div ng-repeat="item in items track by $index">
{{item.id}}
</div>
<div>
Total value: {{totVal}}
</div>
</div>
</div>
And in your controller you will bind your items, like this:
.controller('MainCtrl', ['$scope', function($scope) {
// Are actually fetched from a service
$scope.items = [
{id: 1,..},
{id: 2,..},
..
];
}])
.controller('MyCtrl', ['$rootScope','$scope', function($rootScope, $scope) {
$scope.updateTotVal = function() {
var totVal = 0;
// calculations
console.log(totVal); // Is correct here
$scope.totVal = totVal;
}
$scope.totVal = 0;
// Here $rootScope makes sense
$rootScope.$watch('items', function() {
$scope.updateTotVal();
}, true);
}]);
Edit:
Also, everytime you instantiate your controller, you set $rootScope.totalVal = 0
, eventually resetting it.
Maybe this is why you don't see the update?
Upvotes: 0