Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS not refreshing ngRepeat when updating array

I'm having serious troubles understanding AngularJS sometimes. So I have a basic array in my controller like

$scope.items = ["a","b","c"]

I'm ngRepeating in my template over the items array ng-repeat="item in items". Super straightfoward so far. After a couple of UX actions, I want to push some new stuff to my array.

 $scope.items.push("something");

So, 50% of the time, the new element is added to the view. But the other 50%, nothing happens. And it's like super frustrating; bc if I wrap that within $scope.$apply(), I got a "$digest already in progress" error. Wrapping that into $timeout doesn't help either.

And when I inspect my element scope using the Chrome extension; I can see the new data is there and the $scope.items value is correct. But the view is just not taking care of adding that to the DOM.

Thanks!

like image 662
spacenick Avatar asked Mar 22 '13 12:03

spacenick


2 Answers

You are modifying the scope outside of angular's $digest cycle 50% of the time.

If there is a callback which is not from angularjs; (posibbly jquery). You need to call $apply to force a $digest cycle. But you cannot call $apply while in $digest cycle because every change you made will be reflected automatically already.

You need to know when the callback is not from angular and should call $apply only then.

If you don't know and not able to learn, here is a neat trick:

var applyFn = function () {
    $scope.someProp = "123";
};
if ($scope.$$phase) { // most of the time it is "$digest"
    applyFn();
} else {
    $scope.$apply(applyFn);
}
like image 133
Umur Kontacı Avatar answered Nov 18 '22 23:11

Umur Kontacı


As pointed out by Umur Kontacı, you are making model changes outside of the digest cycle sometimes. However, instead of working around this problem and trying to detect whether you are in an apply/digest context or not, I suggest you make sure this never happens.

The main cause for this problem is that your function is called as a reaction to a DOM event. E.g.

jQuery('.some-element').click(function() { seeminglyProblematicCode() })

This is where your $apply() needs to go, not within the function. Otherwise your whole code will be littered with such distinctions sooner or later. Assuming there is a scope in the context of this event handler, you can write:

jQuery('.some-element').click(function() { 
    $scope.$apply(function() { seeminglyProblematicCode() })
})

However, there is one caveat you have to be aware of: When you trigger a click event from your code, you will run into the problem that a digest cycle is already in progress. This is where you need the $timeout. The answers to this question cover this problem very well.

like image 38
lex82 Avatar answered Nov 18 '22 22:11

lex82