Angular Infinite $digest Loop - javascript

I am working on a site where you can search a food, and see if its a fruit, a vegetable, or neither (because I'm bored). I decided to use Angular, even though I'm pretty new to it.
I started getting this error: $rootScope:infdig
Infinite $digest Loop
That's may or may not be the exact phrasing or the error, because the page lags out so much, I can't open the Javascript console.
This my result view controller:
app.controller('resultController', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$scope.result = $routeParams.query;
$scope.whatIsIt = function() {
$http.get('json/foods.json').success(function(data) {
var lists = JSON.parse(data);
var itIsA = 'uuggghhhhh';
if (lists['fruit'].contains($scope.result)) {
itIsA = 'fruit';
} else if (lists['vegetable'].contains($scope.result)) {
itIsA = 'vegetable';
}
});
}
}]);
Heres my app module:
var app = angular.module('fruitOrNot', [
'ngRoute'
]);
app.config(['$routeProvider' ,
function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'layouts/fruit-search.html',
controller: 'searchController'
}).when('/:query', {
templateUrl: 'layouts/result.html',
controller: 'resultController'
});
}]);
And here is layouts/result.html:
<h1>{{ result }}</h1>
<p>{{ result }} is a {{ whatIsIt() }}</p>
So I am wondering what could be causing that $digest loop error, or how to find out, because I can't use the console. Thanks!
The whole project is here on Github as well.

The problem that you were having was that you were setting a field on the scope, which implicitly calls $digest and lays out the template again. But, laying out the template makes the http request again, and then changes the scope, which calls $digest. And that is the infinite loop.
You can avoid this by ensuring that the http request never gets triggered during a layout of the template.
A more angular correct way of implementing your app would be to extract your GET request into a proper service and then injecting your service into the controller.
Then in the controller, you make the service call, like this:
app.controller('resultController', ['$scope', '$routeParams', 'whatsitService',
function($scope, $routeParams, $http) {
$scope.result = $routeParams.query;
whatsitService.doit().then(function(result) {
$scope.whatsit = result;
}
})
;
Your template would look like this:
<h1>{{ result }}</h1>
<p>{{ result }} is a {{ whatsit }}</p>

The problem is you trying to call a function and publish the return value directly and the value that you are trying to return is out of scope. You need to put that value in scope.
Code snippet:
app.controller('resultController', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$scope.result = $routeParams.query;
$scope.itIsA = "";
$scope.whatIsIt = function() {
$http.get('json/foods.json').success(function(data) {
var lists = JSON.parse(data);
var itIsA = 'uuggghhhhh';
if (lists['fruit'].contains($scope.result)) {
$scope.itIsA = 'fruit';
} else if (lists['vegetable'].contains($scope.result)) {
$scope.itIsA = 'vegetable';
}
});
}
}]);
and in the HTML Template:
p>{{ result }} is a {{itIsA}}</p>

Infinite $digest Loop can happen in this below case if you have bound an expression to it like : {{events()}}
and
$scope.events = function() {
return Recording.getEvents();
}
But not in the below case. even if you have bound {{events}}
$scope.events = Recording.getEvents();
The reason is in the first angular suppose the value is changing in every digest loop and it keeps updating it. In second one it simply wait for the promise.

It is happening because of in your angular expression '{{ whatIsIt() }}' in
"{{ result }} is a {{ whatIsIt() }}" you are calling a function that returns a different value each time invoke thus causing a new digest cycle which in turn invokes the function.
I think you should bind the result of whatIsIt() to a value and then use that value in template. check this out https://docs.angularjs.org/error/$rootScope/infdig

Related

Using $scope in AngularJS callbacks

I am trying to bind data to $scope within a callback function and display this in an html element.
Below is my angular code:
gAutocomplete.controller('geocoder', ['gdata', function($scope, gdata){
var geocoder = L.mapbox.geocoder('mapbox.places');
geocoder.query('New York', assign_geocode2());
function assign_geocode2() {
function assign_geocode(err, data) {
console.log(data);
$scope.lat = data.latlng[0];
$scope.lng = data.latlng[1];
console.log($scope.lat)
}
return assign_geocode;
};
}])
Below is HTML:
</div>
<div class="spacer50"></div>
<div class="center-block" style="width:600px" ng-cloak data-ng- controller='geocoder'>
{{"Chosen lat/long are"}} {{$scope.lat}} {{$scope.lng}}
</div>
I can see the controller gets executed, callback function is called and values are written to console.log. However, they are not propogated to HTML element. What could be happening?
Update
I am not using $timeout as below and getting errors that $timeout is not a function. i know I am using an intermediate tmp variable, but when I use $timeout in the closure, I still have the same issue.
gAutocomplete.controller('geocoder', ['$scope', 'gdata', '$timeout', function($scope, $timeout, gdata) {
var tmp = {}
var geocoder = L.mapbox.geocoder('mapbox.places');
geocoder.query('New York', assign_geocode2(tmp));
function assign_geocode2(tmp) {
function assign_geocode(err, data) {
tmp.lat = data.latlng[0],
tmp.lng = data.latlng[1]
}
return assign_geocode;
}
$timeout(function() {
$scope.lat = tmp.lat,
$scope.lng = tmp.lng,
console.log($scope)},0);
}
])
You're changing scope values from a non-angular event handler. This means you need to notify angular that, "hey, I've updated things, take note pls". AFAIK the ideal way of taking care of this is running the callback inside a $timeout call.
function assign_geocode(err, data) {
$timeout(() => {
console.log(data);
$scope.lat = data.latlng[0];
$scope.lng = data.latlng[1];
console.log($scope.lat)
});
}
Running this inside $timeout will cause angular to run a digest cycle and update scope values. You don't need to do this from events initiated by Angular, because it already knows its in a digest cycle. For example, services like $http take care of this for you.
Scope is the glue between application controller and the view. During the template linking phase the directives set up $watch expressions on the scope. The $watch allows the directives to be notified of property changes, which allows the directive to render the updated value to the DOM.
...
{{"Chosen lat/long are"}} {{lat}} {{lng}}
...
Example :
http://plnkr.co/edit/5TJJkYf21LlwPyyKjgTv?p=preview
https://docs.angularjs.org/guide/scope

AngularJS Proper way to pass variables

I would like to make them global so I can use [color] and [shape] throughout the whole script. I will need each one to update independently but as I continue to add to the site I am going to need to use both together.
Live preview
Example does not work: $scope.shapeSelected = response.data[color][shape];
Example does work: $scope.shapeSelected = response.data.blue[shape];
var app = angular.module("computer", ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/main', {
controller: 'MainCtrl'
}).
otherwise({
redirectTo: '/main'
})
}])
.controller('MainCtrl', ['$scope', '$http', function($scope, $http) {
$scope.colorType = function(color) {
$http.get('stuff.json').then(function(response) {
$scope.colorSelected = response.data.type[color];
});
}
$scope.shapeType = function(shape) {
$http.get('shapes.json').then(function(response) {
$scope.shapeSelected = response.data[color][shape]; // <--- [color] is not getting pulled in on this function.
var resultsColorShape = $scope.shapeSelected; // <--- I would like to be able to store this incase i need it later.
console.log('resultsColorShape');
});
}
}]);
You don't have to pass argument to your functions. if you defined ng-model="Color" you can use $scope.Color in your javascript code:
change in html:
ng-change="colorType()"
ng-change="shapeType()"
and js to:
$scope.colorType = function() {
$http.get('stuff.json').then(function(response) {
$scope.colorSelected = response.data.type[$scope.Color];
});
}
$scope.shapeType = function() {
$http.get('shapes.json').then(function(response) {
$scope.shapeSelected = response.data[$scope.Color][$scope.Shape];
});
}
If your question is about passing the variables or sharing the data properly across functions then this should help you.
In your scenario. As the ng-change functions are assigned.
When the ng-change function is triggered if you don't have a ng-model try to save the new value that is the passed parameter in the $scope so as to access it across all the other functions in that controller.
If you have a ng-model declared for your element then just use the attribute for the ng-model as the reference variable . e.g: ng-model="xyz" then $scope.xyz would give you the desired value of the element.
This is how you can access the element value accordingly

Assign value to controller variable asyncronously - AngularJS

I get data from remote request by companiesData.getCompanies() and put it into controller variable.
The controller does not wait for promise resolution, figuring the response array empty.
JS controller :
angular.module('X.Exh', [])
.controller('ExhibitorsController', function($scope, $state, $stateParams, companiesData) {
this.companies = [];
companiesData.getCompanies().then(function(response) {
this.companies = response.data;
console.log(this.companies); // working very well
});
});
HTML:
<ion-alpha-scroll ng-model="Exh.companies" key="name" display-key="name" subheader="true" use-complete-alphabet="true">
<!-- Basically the ion alpha scroll is just doing a ng-repeat for every item, it is not the problem here -->
Not waiting for the HTTP request, Exh.companies figures empty. (of course if I don't do this.companies = []; at the beginning of my controller, my HTML says that Exh.companies is undefined.
How do I get data properly?
this inside the unnamed function does not influence original this.companies:
angular
.module('X.Exh', [])
.controller('ExhibitorsController', function($scope, $state, $stateParams, companiesData) {
var vm = this;
vm.companies = []; // you can omit this but for documentation and code clear you can declare it;
companiesData.getCompanies().then(function(response) {
vm.companies = response.data;
console.log(vm.companies); // does not point to local this.companies but to the caller context.
});
});
Please note that vm. runs when you use controllerAs pattern.
Alternatively you can simply access $scope variable:
angular
.module('X.Exh', [])
.controller('ExhibitorsController', function($scope, $state, $stateParams, companiesData) {
$scope.companies = []; // you can omit this but for documentation and code clear you can declare it;
companiesData.getCompanies().then(function(response) {
$scope.companies = response.data;
console.log($scope.companies); // does not point to local this.companies but to the caller context.
});
});

Service's promise in AngularJS controller with as syntax

I have problem with below code. I prefer controller as controller syntax and assigning data to this instead of $scope. Problem is that it does not work in below case, everything is fine with $scope.user, but this.user doesn't show anything in template. Did I anything wrong or do I need to use $scope in this case?
angular.module('services', [])
.factory('current_user', ['$http', '$q', function($http, $q){
var deferred = $q.defer();
$http.get('/api/user').success(function(data){
deferred.resolve(data);
});
return deferred.promise;
}]);
angular.module('controllers', [])
.controller('myUser', ['current_user', '$scope', function(current_user, $scope){
current_user.then(function(data){
this.user = data; #does not work
$scope.user = data; #works fine
})
}]);
<div ng-controller="myUser as myUser">
<p>Current user: {{ user }}</p>
<p>Current user: {{ myUser.user }}</p>
</div>
When passing a callback into a function like that, you can't be guaranteed that the context (i.e. this) is what you expect. In this case, this is likely to point to the Promise itself.
To work around this, you can either capture a reference to your controller using var ctrl = this outside of your callback:
angular.module('controllers', [])
.controller('myUser', ['current_user', function(current_user){
var ctrl = this;
current_user.then(function(data){
ctrl.user = data;
});
}]);
The above also is why attaching the property to $scope works, because it's always going to be a reference to your controller in that context.
Or else you can use bind to force the context of your promise callback to be your controller:
angular.module('controllers', [])
.controller('myUser', ['current_user', function(current_user){
current_user.then(function(data){
this.user = data;
}.bind(this));
}]);

data obtained from $http service cant be obtained from ng-click

Here is a part of my controller:
function($scope, $http, $stateParams, $timeout, $q, $ionicPopup, $ionicActionSheet){
$http.get('topics/' + $stateParams.subTopicId + '.json').
success(function(data, status,header, config) {
$scope.questions = data;
});
}
Now here is a part of my view:
<button ng-click="someFunc('{{questions[qNum].opt1}}')">
{{questions[0].opt1}}
</button>
In the above view {{questions[0].opt1}} works perfectly. But when passed as an arg to ng-click it becomes empty.
And again in my controller:
function someFunc(aValue){
alert(aValue); // Empty
}
Why is the value of the variable aValue empty.
On the other hand if, I directly declare $scope.questions inside my controller like this:
$scope.question = [
{ ... }
];
Then someFunc displays the value of the variable correctly. What is going on here?
What am I doing wrong?
Try this:
<button ng-click="someFunc(questions[qNum].opt1)">
Also, you are explicitly binding to element 0 when showing the property, but in your ng-click you are using a variable qNum, so be sure that is defined.

Categories