Why does the following give the error:
Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Code
<div ng-app>
<h2>Todo</h2>
<div ng-controller="TodoCtrl">
<span ng-bind="getText()"></span>
</div>
</div>
function TodoCtrl($scope) {
$scope.todos = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}];
$scope.getText = function() {
var names = $scope.todos.map(function(t) {
return t.text;
});
return names;
}
};
The code block is supposed to grab all todos and then render their names in a list using ng-bind. It works, but tons of digest iteration errors show up in console.
jsfiddle
It is really a bad practice to use a function evaluation in ng-bind, reason for this infinite digest cycle is because your digest cycle never gets settled. Everytime digest cycle happens ng-bind expression also runs and since the return value from ng-bind expression is always different (different object reference produced by array.map) it has to rerun the digest cycle again and it goes on until reached the max limit set, i.e 10.
In your specific case you could just set the names as a scope property and ng-bind="name".
$scope.names = $scope.todos.map(function(t) {
return t.text;
}).join();
As a general rule you can make sure you update the property name only when needed from your controller, example when an event occurs like adding a todo, removing a todo etc.. A typical scenario in this answer. You could also use interpolation instead of ng-bind and use function expression. {{}}. ie:
$scope.getText = function() {
return $scope.todos.map(function(t) {
return t.text;
}).join();
}
and
<span>{{getText()}}</span> <!--or even <span ng-bind="getText()"></span>-->
Fiddle
I feel like you have over complicated this i have updated the fiddle with a working solution http://jsfiddle.net/U3pVM/12417/.
<div ng-app>
<h2>Todo</h2>
<div ng-controller="TodoCtrl">
<div ng-repeat="todo in todos">
<span >{{ todo.text}}</span>
</div>
</div>
</div>
function TodoCtrl($scope) {
$scope.todos = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}];
};
Related
I couldnt fint the answer on stack or google...
Why function in ng-bind call many times ?
html:
<li ng-if="byProviders" ng-repeat="(key, value) in byProviderGames | groupBy: 'provider'">
<p ng-bind="providersNames(key)"></p>
</li>
controller:
$scope.providersNames = function providersNames(key) {
// providersObject's length is 8
var index = $scope.providersObject.findIndex(function(x){ return x.name == key });
// Call more then 1000 times
console.log($scope.gamesProviders[index]);
var title = $scope.providersObject[index].title;
return title;
}
From the ngBind source code we can see that this directive registers a ngBindWatchAction callback to change element.textContent whenever the ng-bind attribute changes using scope.$watch:
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
element.textContent = stringify(value);
});
The watchExpression that is used as a first argument of the scope.$watch method is called on every call to $digest() (at least 1 time) and returns the value that will be watched (but it may be executed multiple times to check if the model was not changed by by other watchers). This is mentioned in the docs:
Be prepared for multiple calls to your watchExpression because it will
execute multiple times in a single $digest cycle if a change is
detected.
In this example $scope.getName method will be called 11 times until its returned value become stable:
angular.module('bindExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.name = 'Arik';
$scope.count = 0;
$scope.getName = function() {
if ($scope.count < 10) { $scope.count++; }
console.log($scope.count);
return $scope.name + $scope.count;
};
}]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body ng-app="bindExample">
<div ng-controller="ExampleController">
Hello <span ng-bind="getName()"></span>!
</div>
</body>
There are a couple things you can do to help with performance and how it affects the number of times the function is called.
First, you can add a track by $index statement to the end of the ng-repeat. It would end up looking like this.
ng-repeat="(key, value) in byProviderGames | groupBy: 'provider' track by $index"
Second, if just displaying data and no other interaction will take place you can unbind them from the scope. So it'll no longer dirty check for changes. It'll end up looking like this.
ng-repeat="(key, value) in ::byProviderGames | groupBy: 'provider'"
Superpower it with both.
ng-repeat="(key, value) in ::byProviderGames | groupBy: 'provider' track by $index"
You can even go as far as to unbind the actual value.
<p ng-bind="::providersNames(key)"></p>
I'm including a code pen, so you can see how many times the function is called. Nowhere as much as you indicated above. Hope it helps. CodePen
Unbind, properly known by (one-time-binding)[https://docs.angularjs.org/guide/expression]
One-time binding
An expression that starts with :: is considered a one-time expression. One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value (see value stabilization algorithm below).
function exampleController($scope) {
$scope.exampleArray = [1, 2, 3, 4];
var exampleLookupArray = [
{ name: "Schnauzer" },
{ name: "Dachshund" },
{ name: "German Shepard" },
{ name: "Doberman Pinscher" }
];
$scope.breedLookUp = function(key) {
console.count();
return exampleLookupArray[key-1].name;
};
}
angular
.module("example", [])
.controller("exampleController", exampleController);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div class="container-fluid" ng-app="example">
<div class="container" ng-controller="exampleController">
<div ng-repeat="ex in ::exampleArray track by $index">
<span ng-bind="::breedLookUp(ex)"></span>
</div>
</div>
</div>
I've created simple custom directive in angularJs. In that directive I am passing an array of objects as tableLayout. Please see my working jsfiddle with no errors.
JS Fiddle Working
However I need to pass a filtered tableLayout. I've created a function in the scope called filterFilterFn to filter the values and then pass it into the scope of my directive. When i do this I get a $rootScope:infdig error.
Js Fiddle w/ filterFunction NOT working
Reading another similar problem it was to do with the using the default filter in angularJs. Hence why I've have done a custom filter function in the scope. But I am still getting a same error. Advice on what I am doing wrong would be appreciated.
Non-working code below:
<div ng-app="myApp" ng-controller="mainCtrl">
<script type="text/ng-template" id="/template">
<button ng-click="testFn()">Test</button>
<div layout="row">
<div flex ng-repeat="col in [1,2,3]"><span>HEADER{{$index}}</span>
<div layout="column">
<div flex style="border: 1px solid black;" ng-repeat="row in [1,2,3]">{{$index}}</div>
</div>
</div>
</div>
</script>
<button ng-click="testFn()">Test 2</button>
<form-table table-layout=formFilterFn('table_id',1)></form-table>
</div>
var app = angular.module('myApp', ['ngMaterial']);
app.controller('mainCtrl', function($scope) {
$scope.tableLayout =[{"head_id":"GAP Assessment","table_id":"1","table_name":"GAP Table","element_id":"0","element_name":"Action Reference","sort_order":"0","is_multirow":"1","flex":"30","element_sort_order":"4","is_show":"0"},{"head_id":"GAP Assessment","table_id":"1","table_name":"GAP Table","element_id":"1","element_name":"Audit Criteria","sort_order":"0","is_multirow":"1","flex":"30","element_sort_order":"0","is_show":"1"},{"head_id":"GAP Assessment","table_id":"1","table_name":"GAP Table","element_id":"3","element_name":"Document Reference","sort_order":"0","is_multirow":"1","flex":"10","element_sort_order":"3","is_show":"1"},{"head_id":"GAP Assessment","table_id":"1","table_name":"GAP Table","element_id":"4","element_name":"Findings - General","sort_order":"0","is_multirow":"1","flex":"20","element_sort_order":"1","is_show":"1"},{"head_id":"GAP Assessment","table_id":"1","table_name":"GAP Table","element_id":"5","element_name":"Findings Details","sort_order":"0","is_multirow":"1","flex":"40","element_sort_order":"2","is_show":"1"}]
$scope.testFn=function(){
console.log("Test");
}
$scope.formFilterFn = function(key,value){
var output = [];
var input = $scope.tableLayout;
for (var x =0; x < Object.keys(input).length; x++){
if (input[x][key]==value){
output.push(input[x]);
}
}
return output;
}
});
app.directive('formTable', function() {
return {
scope:{tableLayout:'='},
link: function(scope,element,attrs){ // normal variables rather than actual $scope, that is the scope data is passed into scope
scope.column=[1,2,3];
scope.testFn=function(){
console.log(scope.tableLayout);
}
//function and scopes go here
},//return
transclude:true,
templateUrl: '/template',
restrict: 'E'
}
})
2 way bindings is causing the loop here, you can bind your scope with '&'.
Please check: http://jsfiddle.net/pa13f6gb/1/
scope:{ tableLayout:'&' },
From https://docs.angularjs.org/guide/directive:
"Because of this, '&' bindings are ideal for binding callback functions to directive behaviors."
I wouldn't personally call it a filter, just to avoid confusion with actual angular filters. Yes it's a filtering function.
The infinite digest is happening because the filter function is returning a new array every time. If you set a variable to the result of the filter, and bind the table to that, it should work.
For example, I want to make a list which looks like:
Apple - Steve Jobs
Microsoft - Bill Gates
Tesla - Elon Musk
And I'm going to do this, using two Firebase apps and AngularJS.
First FB app looks like:
companies
|_Apple: 0
|_Microsoft: 0
|_Tesla: 0
Second FB app:
companies-ceo
|_Apple: "Steve Jobs"
|_Microsoft: "Bill Gates"
|_Tesla: "Elon Musk"
So I have an AngularJs app:
angular.module('App', ["firebase"])
.controller('AppCtrl', ['$scope', '$firebaseObject',
function($scope,$firebaseObject){
var userRef = new Firebase('https://companies.firebaseio.com/');
var userObj = $firebaseObject(userRef);
userObj.$bindTo($scope, "userData");
$scope.getCEO = function(companyName){
var indexRef = new Firebase('https://companies-ceo.firebaseio.com/'+companyName);
var indexObj = $firebaseObject(indexRef);
indexObj.$loaded(function(){
return indexObj.$value;
})
};
}])
And an HTML code:
<div ng-controller="AppCtrl">
<div ng-repeat="(companyName,tmp) in userData">
<p>{{companyName}} - {{getCEO(companyName)}}</p>
</div>
</div>
When I don't use getCEO() function, like:
<div ng-controller="AppCtrl">
<div ng-repeat="(companyName,tmp) in userData">
<p>{{companyName}}</p>
</div>
</div>
everything is alright, but with getCEO() function is causes an error:
Error: 10 $digest() iterations reached. Aborting!
How can I fix it?
Because you called getCEO(companyName) in {{}} directive that are getting evaluated of each digest cycle which is causing digest cycle to reach its limitation and throwing an error. Instead of doing that I'd suggest you to call getCEO(companyName) method on div rendering time of ng-repeat nothing but I'm gonna use ng-init here
Markup
<div ng-repeat="(companyName,tmp) in userData" ng-init="ceo=getCEO(companyName)">
<p>{{companyName}} - {{ceo}}</p>
</div>
OR
Another good example would be using :: bindonce directive will run the specified method runs on only once. By using this directive getCEO(companyName) method will call only once.
Markup
<div ng-repeat="(companyName,tmp) in userData">
<p>{{companyName}} - {{::getCEO(companyName)}}</p>
</div>
NOTE:- Bindonce :: directive will need Angular 1.3+ version
I have a collection of objects, say Products, which I can interact with using $resource. On an index page, I'd like to either display the collection, or, in the case the collection is empty, display a helpful message. i.e.
In Controller
$scope.products = Products.query();
In Template
<div ng-repeat="product in products">
...
</div>
<div class="alert" ng-hide="products.length">
<p>Oops, no products!</p>
</div>
This works fine, provided the user isn't staring at the spot where the ng-repeat will occur. If they are, or if there is a delay in the response from the server, they may notice a slight flicker, before the promise is resolved.
Given that, "invoking a $resource object method immediately returns an empty reference" (see here), such a flicker will always in this example. Instead, I find myself writing:
<div class="alert" ng-hide="!products.$resolved || products.length">
<p>Oops, no products!</p>
</div>
Which takes care of the flicker. However, I'm not too keen on letting my view know exactly how the products are obtained. Especially if I change this later on. Is there anything cleaner I could do? I'm aware that a fallback for ng-repeat is in the works (see here), however, just wondering if there's a cleaner solution in the meantime.
You could use the success method to set the object:
Products.query(function(data) {
$scope.products = data;
});
Or use the promise:
Products.query().$promise.then(function(data) {
$scope.products = data;
});
This way, the object doesn't become empty until you get a response.
You can get $promise out of $resource and change displayed information before/after promise is resolved.
Say you have following Products and service to get them.
/* products */
[
{ "id":1, "name":"name1" },
{ "id":2, "name":"name2" },
...
]
/***/
app.factory('Products', function ($resource) {
return $resource('products.json');
});
Then in your controller assign data only after promise is resolved.
app.controller('MainCtrl', function ($scope, Products) {
$scope.initialize = function () {
$scope.products = null;
Products.query().$promise.then(function (data) {
$scope.products = data;
});
};
$scope.initialize();
});
In your HTML template you can take care of the cases like a) not yet resolved b) resolved c) resolved but no data
<body ng-controller="MainCtrl">
<div ng-show="!products">
Getting data... please wait
</div>
<div ng-show="products && products.length === 0">
Oh noes!1 :( No products
</div>
<div ng-show="products">
<span ng-repeat="product in products">
{{ product | json }} <br>
</span>
</div>
<div>
<button type="button" ng-click="initialize()">Refresh</button>
</div>
</body>
Related plunker here http://plnkr.co/edit/Ggzyz9
I'm creating an ajax search page which will consist of a search input box, series of filter drop-downs and then a UL where the results are displayed.
As the filters part of the search will be in a separate place on the page, I thought it would be a good idea to create a Service which deals with coordinating the inputs and the ajax requests to a search server-side. This can then be called by a couple of separate Controllers (one for searchbox and results, and one for filters).
The main thing I'm struggling with is getting results to refresh when the ajax is called. If I put the ajax directly in the SearchCtrl Controller, it works fine, but when I move the ajax out to a Service it stops updating the results when the find method on the Service is called.
I'm sure it's something simple I've missed, but I can't seem to see it.
Markup:
<div ng-app="jobs">
<div data-ng-controller="SearchCtrl">
<div class="search">
<h2>Search</h2>
<div class="box"><input type="text" id="search" maxlength="75" data-ng-model="search_term" data-ng-change="doSearch()" placeholder="Type to start your search..." /></div>
</div>
<div class="search-summary">
<p><span class="field">You searched for:</span> {{ search_term }}</p>
</div>
<div class="results">
<ul>
<li data-ng-repeat="item in searchService.results">
<h3>{{ item.title }}</h3>
</li>
</ul>
</div>
</div>
</div>
AngularJS:
var app = angular.module('jobs', []);
app.factory('searchService', function($http) {
var results = [];
function find(term) {
$http.get('/json/search').success(function(data) {
results = data.results;
});
}
//public API
return {
results: results,
find: find
};
});
app.controller("SearchCtrl", ['$scope', '$http', 'searchService', function($scope, $http, searchService) {
$scope.search_term = '';
$scope.searchService = searchService;
$scope.doSearch = function(){
$scope.searchService.find($scope.search_term);
};
$scope.searchService.find();
}]);
Here is a rough JSFiddle, I've commented out the ajax and I'm just updating the results variable manually as an example. For brevity I've not included the filter drop-downs.
http://jsfiddle.net/XTQSu/1/
I'm very new to AngularJS, so if I'm going about it in totally the wrong way, please tell me so :)
In your HTML, you need to reference a property defined on your controller's $scope. One way to do that is to bind $scope.searchService.results to searchService.results once in your controller:
$scope.searchService.results = searchService.results;
Now this line will work:
<li data-ng-repeat="item in searchService.results">
In your service, use angular.copy() rather than assigning a new array reference to results, otherwise your controller's $scope will lose its data-binding:
var new_results = [{ 'title': 'title 3' },
{ 'title': 'title 4' }];
angular.copy(new_results, results);
Fiddle. In the fiddle, I commented out the initial call to find(), so you can see an update happen when you type something into the search box.
The problem is that you're never updating your results within your scope. There are many approaches to do this, but based on your current code, you could first modify your find function to return the results:
function find(term) {
$http.get('/json/search').success(function(data) {
var results = data.results;
});
//also notice that you're not using the variable 'term'
//to perform a query in your webservice
return results;
}
You're using a module pattern in your 'public API' so your searchService returns the find function and an array of results, but you'd want to make the function find to be the responsible for actually returning the results.
Setting that aside, whenever you call doSearch() in your scope, you'd want to update the current results for those returned by your searchService
$scope.doSearch = function(){
$scope.searchService.results = searchService.find($scope.search_term);
};
I updated your jsfiddle with my ideas, is not functional but i added some commments and logs to help you debug this issue. http://jsfiddle.net/XTQSu/3/