I'm running this code in an Angular service, immediately upon loading the page. The controller $scope is passed as an argument to the function this extract belong to. The function is a $q promise.
I am not able to figure out how can I let the controller know that scope.req.rows has been updated. If I add scope.$apply() right after it, I run into a running digest phase. If I use the $q resolve function, it returns and no more loop results are returned. scope.$evalAsync() and $timeout seem to have no effect (at least without setting a timeout > 0). Same goes for scope.$watch.
How can I let the controller know that values were updated?
for (var page = 0; page < nbPages; page++) {
(function (pageNum) {
that.get(url,
where,
res.pageSize * page,
res.pageSize)
.then(function Success(data) {
$log.info('Result of page ' + pageNum + ' received');
for (row in data) {
scope.req.rows++;
}
}).catch(function chunkFail(err) {
reject(err);
});
})(page);
I build simple demo and it`s works. Correct me if i wrong.
Updated:
i mocking http request and delay it form 1000ms to 30000ms. and i steel have't any scope problems.
http://jsbin.com/wasoxe/4/edit?js,output
Related
I'd like to check an image to see if the resource is available, before displaying it. I found a good way to do that in AngularJS here: Angular js - isImage( ) - check if it's image by url
But every time I try to implement it, an infinite loop is triggered, even if I reduce the function to its simplest form in a codepen : https://codepen.io/anon/pen/mBwgbE
test image function (js)
$scope.testImage = function(src) {
console.log('function triggered');
Utils.isImage(src).then(function(result) {
return "result";
});
};
Usage (html)
<h3>Image link broken<h3>
<p>{{testImage('anylink')}}</p>
<h3>Image link OK<h3>
<p>{{testImage('http://lorempixel.com/400/200/sports/1/')}}</p>
Can anyone explain this behaviour to me, and help me fix it?
Angular runs the digest loop, and interprets your template. It sees {{testImage('anylink')}} and calls into it. This calls into Utils.isImage, which creates a promise. The promise is returned to testImage, but testImage itself doens't return anything, so the template shows nothing.
A little later, the promise resolves. Angular sees this, so it runs the digest loop and interprets your template. It sees {{testImage('anylink')}} and calls into it. This calls into Utils.isImages, which creates a prom... oh crap, we're in an loop. It's going to call isImage, which creates a promise, and then when that promise resolves, it interprets the template again and calls isImage, starting all over.
Instead, i would recommend that when your controller loads, you create the promises right then, and when they resolve, you stick whatever values you need from them onto the controller as concrete values. Something like this:
function myController($scope, Utils) {
$scope.anyLink = null;
$scope.sportsLink = null;
Utils.isImage('anyLink')
.then(function (result) { $scope.anyLink = result });
Utils.isImage('http://lorempixel.com/400/200/sports/1/')
.then(function (result) { $scope.sportsLink = result });
$scope.heading = "My Controller";
}
And then on your template, interact with $scope.anyLink or $scope.sportsLink
$scope.testImage is automatically watched by angular to see change of testImage.
As a result you can stop infinite loop by using $scope.cache variable.
$scope.testImage = function(src) {
console.log('function triggered');
if($scope.cache[src] == "result")
return "result";
Utils.isImage(src).then(function(result) {
$scope.cache[src] = "result";
return "result";
});
};
It was tested on your codepen.
I have some code in an Angular directive that looks like the following:
$scope.populateResults = function (list_results) {
$scope.$apply(function() {
console.log("Applying", list_results);
if (list_results.attractions.length === 0) console.log("Empty list");
$scope.attractionsList.list_results = [];
$.each(list_results.attractions, function(index, attraction) {
console.log("executing for each");
if (attraction) {
attraction.addedWaypoint = false;
$scope.attractionsList.list_results.push(attraction);
}
});
console.log($scope.attractionsList.list_results);
});
};
The function is passed as a callback to an AJAX request that is handled in another file. The console.log statements all print everything as expected, but the view does not change. When I print the value of $scope.attractionsList.list_results in another function, it shows that the list is empty. Shouldn't the fact that the $scope variable is changed in an $apply function mean that the view would be updated? This only happens occasionally. I don't see any errors coming back from the AJAX request, and all the console.log's in the $.each loop print as expected. Where else should I look for this bug?
I am reading AngularJS in Action by Lukas Ruebbelke to clear the concept of dirty checking as to how AngularJS works at a molecular level.
The author puts forward,
It is during the digest cycle that all watch expressions for a scope object
are evaluated. When a watch expression detects that a $scope property has
changed, then a listener function is fired.
Ocassionally a property is changed without AngularJS knowing about it. You
can manually kickstart a digest cycle vis $apply.
So, my question is what are those situations in a real web application when I need to kick off this digest cycle manually. And are those situations often seen? Kindly suggest.
This will come up any time an asynchronous callback returns from a non-angular library. e.g.
setTimeout(function() {
$scope.myVar = 1;
//Angular doesn't know when setTimeout finishes
//so you have to manually kick off a digest cycle.
$scope.$apply();
});
Angular has the $timeout service which takes care of starting a digest cycle for you but if you are using some third party library that takes a callback and doesn't have an angular wrapper then you will have to do this.
These situations can happen when using 3rd party libraries which provide some kind of data for example.
Say you use library-X which fires an event when something happened and new data is available, which you would like to render with AngularJS.
In these causes AngularJS does not know that data in the scope changed if you just directly set the variables.
That is why you should only modify scope variables inside the $apply function:
function MyController($scope) {
$scope.load = function() {
$scope.message = 'Loading...';
setTimeout(function() {
$scope.$apply(function () {
$scope.message = 'Finished loading!';
});
}, 2000);
}
}
It is also advised to use $scope.$apply(function () { /* update code */ }) instead of the single $scope.$apply() call, since it will properly catch errors and run the diggest regardless of any errors.
I've just started using Angular with Ionic and so far it's great except for a problem with my ecommerce app where I am doing two ajax requests, one to load the vendor name and the other to load the products. I know I could put this in the same request but for other reasons it needs to be kept it separate.
Sometimes the vendor name gets set and sometimes it fails. It seems to be completely random and if I put a debugger in place it always works so appears to be something to do with timing.
.controller('VendorProductsCtrl', function($scope, $stateParams, $http) {
var vendor_name_url = 'URL FOR VENDOR NAME';
$http.get(vendor_name_url)
.success(function(response){
$scope.vendor_name = response.meta.vendor_name;
//debugger; this always works!
})
.error(function(data) {
console.log('server error');
})
$scope.products = [];
$scope.loadMore = function() {
var url = 'URL FOR PRODUCTS';
$http.get(url)
.success(function(response){
$scope.products = response.products;
})
.error(function(data) {
console.log('server error');
})
};
})
There seems to be something fundamental that I am missing on my new quest into Angular land. Thanks for your help.
P.S I'm using Ionic's infinite scroll which is calling loadMore()
Got it working, but seems a bit dodgy!
setTimeout(function () {
$scope.$apply(function () {
$scope.vendor_name = response.meta.vendor_name;
});
}, 500);
I'm not keen on waiting for 500ms or whatever it's set to if it's ready earlier, is there a better way?
Use $q.all
$q.all([
$http.get(vendor_name_url),
$http.get(url)
]).then(function(_responses){
$scope.vendor_name = _responses[0].meta.vendor_name;
$scope.products = _responses[1].products;
})
Seems to be an issue with the http request for the vendor name being finished before the $scope is applied when it works.
Move the definition of your $scope.vendor_name outside of the promise callback.
$scope.vendor_name = '';
$http.get(vendor_name_url).success(function(response){
$scope.vendor_name = response.meta.vendor_name;
})
.error(function(data) {
console.log('server error');
});
loadMore() works correctly because the $scope has the variable of products = []; during the wire-up of the controller. Everything after that hits the scope lifecycle of updating.
EDIT:
I would try to avoid doing the $scope.$apply function as you will then start using it everywhere and it makes the maintenance of your services and controllers very difficult.
Another way to approach this issue is to use resolves on your route.
http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx
This is a great approach to getting multiple data pieces into your controller defined by the route. The wire-up of the controller by the route is responsible for ensuring that data is available for dependency injection on the controller when it runs.
In an AngularJS project, I'm able to display a list of events with a call to $scope.loadEvent, but then not able to update the view when polling the server using $http and $timeout. I can see that the correct $http calls and responses are being made/received.
A condensed version of the controller:
function EventsCtrl ($scope, $http, $timeout) {
$scope.myEvents = new Array();
var newMyEvents = new Array();
$scope.loadEvent = function(eventId) {
$http.get('http...').success(function(result) {
$scope.myEvents.push(result.data);
});
}
$scope.polling = function () {
var poller = function() {
newMyEvents = [];
for(var i=0; i< $scope.myEvents.length; i++) {
$http.get('http...').success(function(result) {
newMyEvents.push(result.data);
});
}
$timeout(poller, 2000);
}
$scope.myEvents = newMyEvents;
poller();
}
}
and the view:
<div ng-controller="EventsCtrl" ng-init="polling()">
I've tried $scope.$apply(), which returns Error: $apply already in progress as it seems $http is already "inside" Angular.
Any and all thoughts appreciated. Thanks.
In some cases you will need to use $scope.$apply();
Try performing:
$scope.$apply(function() {
$scope.myEvents = newMyEvents;
}
EDIT
I misunderstood what's happening completely. The problem is that you are firing all of those queries asynchronously, so you have no way of knowing when each of them finishes. Also, $timeout automatically executes $scope.$apply for you. If you want to be in control of when to actually $apply you can use setTimeout() instead.
Perhaps you could perform $scope.myEvents = newMyEvents within the callback of the last $http request (you don't have 100% guarantee it would actually get the data last). Best way would be executing one call to the backend that fetches all of the resource for you so that you can update the variable in the .success function directly. (if that is achievable)
More about $apply and how to prevent the error