Angular JS Error: 10 $digest() iterations reached. Aborting - javascript

I'm using angularjs-google-maps and am getting an error when trying to loop over my customers as markers on a map.
<map>
<custom-marker ng-repeat="cust in customers" position="[ {{ cust.lat }}, {{ cust.lon }} ]">
<div class="{{ cust.category }}">{{ cust.name }}</div>
</custom-marker>
</map>
The error seems to have something to do with cust.category and cust.name, as when I remove these they work fine.
This is the first couple lines of the error message I'm getting:
Watchers fired in the last 5 iterations: [["[ cust.category , cust.name ]; newVal:
[\"pro\",\"Fred\"]; oldVal: [\"pro\",\"Fred\"]","fn: function (context) {\n
try {\n for(var i = 0, ii = length, part; i<ii; i++) {\n
Full error message here.
Any help with this is appreciated. Thanks in advance!
UPDATE
Code for the custom-marker directive that's part of angular-google-maps is here.

It seems like the digest cycle is stuck in a loop. Angular has a digest cycle where they watch models (whatever is on $scope) and apply changes to the views if the models change. If in a digest cycle you're executing some function that changes a value again, you're triggering another digest cycle which triggers that same function again, changes a value on the model and triggers an infinite loop of digest cycles.
That said, you might want to add the code for your customMarker directive for answers to be more precise.

why are you binding class="{{ cust.category }}",try using ng-class.Also can you use track by $index in ng-repeat.I was getting this error when running my ionic app on ios9.They released a patch to fix this.

Code like this:
scope.$watch('[' + varsToWatch.join(',') + ']'...
looks like "[obj1.prop, obj2.prop]" after concatenation.
Expressions, like this parsed by $parse and cause to evaluate self to new instance of array every iteration, even if nothing changed inside.
You should interpolate/parse it before call $watch and write a condition, which changes only than related data really was changed.
$watch is a function(watchExp, listener, objectEquality, prettyPrintExpression)
You can try to use third parameter(objectEquality) to compare arrays by value, but not by reference equality.
#param {boolean=} objectEquality Compare for object equality using {#link angular.equals} instead of comparing for reference equality.

Related

Infinite $digest() loop AngularJS with filter expression on html attribute

When I try to pass a filter expression inside a component's attribute, e.g. (see this Plunker example as well, and the possible solutions listed below).
<todo-list todos="$ctrl.todos | filter:{type:1}"></todo-list>
I get an error on the infinite digest loop, I don't understand why:
Error: [$rootScope:infdig] http://errors.angularjs.org/1.6.3/$rootScope/infdig?p0=10&p1=%5B%5B%7B%22ms…e%2C%22type%22%3A1%2C%22%24%24hashKey%22%3A%22object%3A5%22%7D%5D%7D%5D%5D
at eval (angular.js:38)
at m.$digest (angular.js:18048)
at m.$apply (angular.js:18280)
at eval (angular.js:1912)
at Object.invoke (angular.js:5003)
at c (angular.js:1910)
at Object.Pc [as bootstrap] (angular.js:1930)
at execute (VM877 main.ts!transpiled:21)
at f (system.js:5)
at Object.execute (system.js:5)
Code, see Plnkr: http://plnkr.co/edit/JdiLEIyji2pHd3eeNMUL?p=preview
Screenshot / Image:
Workaround/solution:
I have several workarounds/solutions:
In the repo where I had the problem at first: I did <todo-list todo-items="$ctrl.todoItems" filter-by="{completed:true}"></todo-list>. For full source see here: https://github.com/aredfox/todo-angularjs-typescript/commit/e71900b96173b63ebcebb8e6c1fba00fe3997971. But I feel it's working around the problem, plus I don't understand why this triggers a $digest() cycle and why it shouldn't just work.
Answer by #Mistalis https://stackoverflow.com/a/43120388/1155847 whogave a somehwat similar solution.
The goal of filter is to get an array as input and return another
array based on some rules and conditions, where array items have the
same structure as input.
The reason that causes an infinite loop in the $digest cycle is that
in a filter, each digest cycle filter returns a different object that
causes an additional cycle. - Source
I would suggest you to move the filter to the todoList directive:
<div ng-repeat="todo in $ctrl.todos | filter: {type:1}">
<span>{{todo.name}}</span>
</div>
If type needs to be dynamic, pass it as a parameter/attribute to the directive.
Forked your Plunker
There's an open issue at the angular.js repo you can follow up on in order to see the evolution regarding this problem:
https://github.com/angular/angular.js/issues/14039
As a temporary solution, you could change your binding to use a shallow two way binding by changing < to =* as mentioned here: https://github.com/angular/angular.js/issues/14039#issue-133588717
Il8PNgcE?p=preview

Why is a function in ng-repeat called several times?

I want to supply a ng-repeat element by a controller function as follows:
<div ng-repeat="picture in allPictures(data.pictures)"></div>
$scope.allPictures = function(pictures) {
alert("function called");
//return... extract all pictures and return as array
}
Result: my allPictures function is called several times, even though I'd expect it to be called only once and then iterate over the results.
Why? And moreover: how can I prevent this and really call the method only once for picture supply?
I would really avoid calling a function insides a ngRepeat attribute, since it will give errors and unexpected behaviour.
But to be honest I dont think that you would need to call a function inside a ngRepeat. I would suggest to do the following:
<div ng-repeat="picture in allPictures"></div>
$scope.getPictures = function(pictures) {
alert("function called");
//return... extract all pictures and return as array
};
$scope.allPictures = $scope.getPictures();
This way the $scope.getPictures function will get called and the $scope.allPictures will be created. ngRepeat can call that collection instead of a function.
See also my Fiddle: https://jsfiddle.net/ABr/w6kc8qyh/1/
a little bit about the digest cycle:
we need to check every time something in the application changes - what was effected by that change, and re-evaluate all the places that might depend on that change,
so that is why the function in the ng-repeat was called multiple times - it had to check wheter the repeated list is the same after some changes happend in the application
read more about the digest cycle and two-way data binding:
http://blog.bguiz.com/post/60397801810/digest-cycles-in-single-page-apps/

function called multiple times from template

In my template I have something like this:
{{formatMyDate(date)}}
but date is a scope variable that is not immediately available so this expression will call the function formatMyDate() many times (returning undefined) before returning the correct value.
I could check if the date is not null within the function but I guess it would be more clean NOT to call the function at all if the date is null.
Any way to achieve this?
Would a custom filter help me out here?
EDIT:
It was suggested that this behaviour could be normal, depending on the $digest cycle.
I've then put a scope.$watch to verify how many times the value of date is changing.
Note that I'm defining these in a directive.
scope.$watch('date', function(value){
console.log('watched_date: ' + value)
})
and I've introduced a console.log() on my formatMyDate function as well
scope.formatMyDate = function(date){
console.log("called_date: " + date)
return dateService.format(date, 'YYYY-MM-DD')
}
Inspecting the console I get (pseudo code)
called_date: undefined
watched_date: undefined
called_date: undefined // many many times (around 20/30)
called_date: correctValue //2 or 3 times
watched_date: correctValue
called_date: correctValue //other 3/4 times
I'm wondering if this is still due to the $digest cycle or it is a bug in my code
I would recommend you to do things differently:
Either use the date $filter or if you are doing something VERY unique and the date $filter is not good enough for you, then you could create your own $filter, like this:
app.filter('formatMyDate', function () {
return function (date) {
if (!date) return "";
var result;
//your code here
return result;
};
});
And use it like this in your template:
{{date | formatMyDate}}
UPDATE:
I guess that I didn't quite answer your question, I just gave you advice on how to improve your code. This time I will try to answer your question:
The $digest cycle is the stage in which Angular ensures the changes of the model have settled,
so that it can render the view with the updated changes. In order to do that,
Angular starts a loop in which each iteration evaluates all the template expressions
of the view, as well as the $watcher functions of the $scope.
If in the current iteration the result is the same as the previous one,
then Angular will exit the loop. Otherwise, it will try again.
If after 10 attempts things haven't settled, Angular will exit
with an error: The "Infite $digest Loop Error" (infdig).
That's why the first time that the $digest cycle runs all the expressions are evaluated (at least) twice. And then Every time that you make a change to the $scope or that one of the $watchers of the $scope gets triggered, the $digest cycle will run again in order to make sure that things have settled, so your expressions will be evaluated again. This is how Angular makes "data-binding" happen, it's a normal behaviour.
So in your case, when in your template you do this: {{formatMyDate(date)}} or this {{date | formatMyDate}} you're defining Angular expressions that will be evaluated every time that the $digest cycle runs, which as you can imagine is very often. That's why is very important to make sure that the $filters (or functions) that you use in your view are efficient and stateless.
You can do this:
{{date && formatMyDate(date)}}
will only execute the second case if the first condition exists and is different from null and undefined.
Check this fiddle: http://jsfiddle.net/HB7LU/7512/

`ng-model` of AngularStrap `bs-typeahead` not available in scope?

I am running into an issue with the value of ng-model, applied to an element using AngularStrap's bs-typeahead, is not accessible within scope. It is however viable from a {{ var }} within the HTML.
I have the following HTML:
<input type="text" placeholder="add a destination" ng-options="item as item for item in modelTypeahead" ng-model="selectedDestination" bs-typeahead data-template="templates/SrcDstTypeaheadTemplate.html">
I initialize the variable in my controller:
$scope.selectedDestination = "";
Placing a {{ selectedDestination }} elsewhere within the HTML works as expected.
However, when I do a console.log($scope.selectedDestination); within my controller it comes out as an empty string.
If I update my initialization to be something, for example:
$scope.selectedDestination = "abc123";
... both the <input> and the {{ selectedDestination }} update appropriately. My console.log will also spit out the set value. However, if I update the typeahead the {{ selectDestination }} will update but my console.log will spit out 'abc123' still.
Is there a scope issue that I am missing? I don't understand how {{ selectedDestination }} is putting out the correct string but the console.log is putting out something different. It would almost seem my binding is one-way, but AngularStrap's bs-typeahead should be two-way (per all the examples).
Where are you doing console.log? You would have to make sure that the value has changed before you do that for the value to show up, you could do:
$scope.watch('selectedDestination', function() {
console.log($scope.selectedDestination);
});
I'm not sure if you're still running into any issues with this, but I found a solution to the exact same problem when I ran into it just recently. I'm almost positive that its a scope issue or apply failure within the AngularStrap stuff, but I wouldn't know where to start looking.
Really, I'm not educated enough to give you the exact reasons that this works, but this is what you do:
(1) You change the variable to an object.
when you put the model and watch on an object instead of a top layer variable, it works better through layers of directives. Don't ask me why....
(2) Use a deep watch on the object you just created.
When you change it to an object, you need to use a deep watch on the variable or the $apply and $digest won't pick up any changes. This is because by default the value will be checked for "reference" equality instead of "value" equality. This breaks because the object's "reference" doesn't change, only its values. But be careful using this deep comparison because the extra effort can cause a lot of overhead.
Here's an example in use with AngularStrap's typeahead:
$scope.selectedDestination = {};
~~~
<input type="text" placeholder="add a destination" ng-options="item as item for item in modelTypeahead" ng-model="selectedDestination.destination" bs-typeahead data-template="templates/SrcDstTypeaheadTemplate.html">
~~~
$scope.$watch('selectedDestination', function(value) {
console.log('selectedDestination', $scope.subComponent);
}, true); //here we need to tell the watch to do a deep watch
EDIT I've traced my issues back to a few things, but part of it was the $render function. I'll keep looking into it. Good luck!

How to Update a Parent Scope Variable from within ng-repeat Expression?

I've run into a situation where I had to update a scope variable from within an ng-repeat expression (ng-class call within ng-repeat in fact).
<div id="page" ng-controller="MainCtrl">
.
<section id="body" ng-init="countMis = {num:0}">
.
<tbody>
<tr ng-repeat="upperCaseLetter in alphabet.specialsUpper" ng-controller="letterController">
<td>{{upperCaseLetter}}</td>
<td>{{filteredLetter=(upperCaseLetter | lowercase)}}</td>
<td>{{alphabet.specialsLower[$index]}}</td>
<td><span ng-class="lowercaseEqual($index,filteredLetter)"></span></td>
</tr>
</tbody>
and in app.js:
function letterController($scope)
{
$scope.lowercaseEqual = function(indx,letter)
{
var returnStr="";
if(letter == $scope.alphabet.specialsLower[indx])
{
returnStr = "glyphicon glyphicon-ok";
}
else
{
$scope.countMis.num = $scope.countMis.num + 1;
returnStr = "glyphicon glyphicon-remove";
}
return returnStr;
};
}
Ng-repeat is in a child controller and data I want to update is in its parent controller. I know you've read same question many times, please keep reading and see JsFiddle example.
So expression function checks filtered data of that particular ng-repeat iteration, if it doesn't match with some other corresponding parent scope variable it returns a class but also it should update parent scope counter.
As those expressions don't evaluate just once, but at least once (because of dirty check in digest) it results counter being incremented more than once for each case.
I've solved my problem by counting classes applied to that particular data after ng-repeat (you'll see in the JsFiddle example).
However I think I may run into same problem in the future, so I want to learn how to update parent scope within an expression of ng-repeat iteration.
I've put a second JsFiddle, I've tried using a factory on both parent controller and child, hoping to update a common variable for both controllers. When I log (consol.log) each iteration and factory method variable, it shows iteration times + 1 (still false value), but it's not reflected on page expression...doesn't make sense at all.
I'd appreciate any help. Thanks.
JsFiddle 1 JsFiddle 2
Since watched expressions (like the one implicitely set up by the ng-class directive) "can execute multiple times per $digest() and should be idempotent".
You should understand what the $watch() and $digest() functions do and look for resources regarding Angular's digest cycle.
So, incrementing a counter inside a watchExpression is not a good idea.
Counting classes seems ok though.
BTW, the factory-approach will help you share data across scopes, but it won't help with the "multiple executions per digest cycle" problem. Furthermore, it might be redundant in your situation, since the child- and parent-scopes can share data directly.
In any case, for the results to be displayed you have to "bind" the factory's getMismatchCount() function with the getMismatchCount() function that you use in your HTML (in Mismatches Count from Factory: {{getMismatchCount()}}):
function mainCtrl($scope, $window, repeatFactory)
...
$scope.getMismatchCount = repeatFactory.getMismatchCount;

Categories

Resources