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.
Related
I have a an issue where it seems as though my controller is not waiting on the http.get '.then'. I am getting data properly back but it seems as though another function is processing before the data is retrieved. I've gone through many posts and have tried many of the things mentioned in those posts, but it doesn't seem to help. I am using PHP to retrieve the data.
I have a HTML file that calls two functions (I had tried with one, but when that didn't work, I tried splitting up the functionality).
HTML of the calls
<form editable-form name="editableForm" onaftersave="fetch();updateDetailsData()" >
Controller functions
$scope.fetch = function() {
$http.get("api/checkSave/"+ JSON.stringify($scope.programDetails))
.then(function(data) {
$scope.okToSave = data.data.save;
$scope.missFields = data.data.fields;
console.log($scope.okToSave); // line #194
console.log($scope.missFields); // line #195
});
}
$scope.updateDetailsData = function(){
console.log($scope.okToSave); // line #202
}
What displays in the console shows:
undefined // line 202
false // line 194 - correct data
Object // line 195 - correct data
As you can see, it appears to be processing the function updateDetailsData before the fetch function finishes. I thought the then should make processing wait until the get is finished - the promise returned.
I need to do some processing in the updateDetailsData function based on the values in the $scope variables but when it gets there they are undefined.
Can someone help? I'm sure it is something little that I am missing, but I think I have tried just about all solutions provided on these forums and still end up with the same results.
The problem comes from: onaftersave="fetch(); updateDetailsData()".
The update function executes as soon as fetch returns, not as soon as fetch is resolved.
Rework your function a bit:
function fetch () {
return $http.get("api/checkSave/"+ JSON.stringify($scope.programDetails))
.then(function(data) {
// ...
});
}
$scope.fetchAndUpdate = function () {
fetch().then(updateDetailsData);
}
In the template:
<form editable-form name="editableForm" onaftersave="fetchAndUpdate()">
You're using promises, so you have to wire into them, they aren't blocking:
<form editable-form name="editableForm" onaftersave="fetch().then(updateDetailsData)" >
Controller functions
$scope.fetch = function() {
return $http.get("api/checkSave/"+ JSON.stringify($scope.programDetails))
.then(function(data) {
$scope.okToSave = data.data.save;
$scope.missFields = data.data.fields;
console.log($scope.okToSave); // line #194
console.log($scope.missFields); // line #195
});
}
$scope.updateDetailsData = function(){
console.log($scope.okToSave); // line #202
}
Your fetch function is calling $http.get which makes an asynchronous call to your server. What that means is that the call will return right away, it won't block (i.e. stop the code from executing) while it waits for the server to respond. That is why you provide a callback using the .then function.
So taken the way you have currently written it, it is working as designed. If you want to have updateDetailsData function be executed after your then code then you have to either put it inside of the then or chain the functions together like Michael P. Bazos or Matthew Berg suggested.
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'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
I'm using the javascript sdk plugin for facebook to create a feed on my webpage.
The problem is that sometimes during load the feed gets unordered, even if i have setup a callback chain.
I think it gets unordered because sometimes the "second" async call gets processed faster than the "first" async call.
This is the first time i've been using callbacks, am i doing it right?
How can i solve the feed gets unordered if some calls finish faster than others?
The code below is only the relevant code and is under working status.
function initFeed(){
FB.api('/{id}/feed', function(response){
var feedArray = response.data;
$.each(feedArray, function(){
var $this = $(this)[0]; //Status Object for single Status in Feed
setStatus($this, processStatus); //processStatus is function defined below
});
});
}
function setStatus(statusObject, callbackProcessStatus){
FB.api("/{personId}?fields=id,link,name,picture",
function (response) {
var html = /* Generates html based from statusObject and response */
callbackProcessStatus(html);
});
}
function processStatus(html){
$('#fb-status-wrapper').append(html);
}
(was uncertain on the title of this post, please edit if you think it is not descriptive enough)
Best regards
This is a somewhat common problem with parallel async calls. The simplest solution requires promises. I recommend the Bluebird promise library, but most will do fine.
var fbApi = function(url){
return new Promise(function(resolve, reject){
FB.api(url, function(resp){ resolve(resp); });
});
}
function setStatus(statusObject){
return fbApi("/{personId}?fields=id,link,name,picture")
.then(function(response){
var html = ...;
return html;
});
}
function getFeedItemPromises(){
return fbApi("/{id}/feed").then(function(response){
return response.data.map(function(item){
});
});
}
Depending on your needs, initFeed could be one of these. The first renders the feed when all items are available, and the second renders it when each item is available, but enforces the order.
function initFeed(){
return Promise.all(getFeedItemPromises())
.then(function(itemsHtml){
// append all of the items at once
$('#fb-status-wrapper').append(itemsHtml.join("\n"));
});
}
Or this which ensures the order, but eagerly appends items to the feed, after all previous items have been added.
function initFeed(){
function renderItem(html){
$('#fb-status-wrapper').append(html);
}
// reduce can be used to chain promises in sequence
return getFeedItemPromises().reduce(function(p, nextPromise){
return p.then(function(){ return nextPromise })
.then(renderItem);
}, Promise.resolve())
}
An alternative would be to create a div for each item which acts as a placeholder, keep those in an array, and fill them in when each resolves. This works especially well if you know the height of the items beforehand, and fade them in when they load. From a UX perspective, this is the best in my opinion.
I would not recommend the above if you don't know the heights of items, as it'll cause headache inducing shifting of items as new ones are inserted.
Indeed you cannot rely on the order in which the requests will finish. The only way to be sure, is to only call the second one if the first one is done. But that will slow down the loading quite a lot.
Another possibility is to remember for each request which one it is, and insert the items in the right order (insert before a 'later' one, even if that one was received earlier).
I think the easiest way to do that, is to make placeholders for the items inside the each loop, so the placeholders are inserted in the right order. When the requests return, you just place the responses in the right placeholder.
It could look somewhat like this. 2 extra lines and a couple of tiny changes. I couldn't test this without the API, but I hope you get the idea.
function initFeed(){
FB.api('/{id}/feed', function(response){
var feedArray = response.data;
$.each(feedArray, function(index){
var $this = $(this)[0]; //Status Object for single Status in Feed
// Make a container per item inside the wrapper.
var $itemContainer = $('<div></div>');
$('#fb-status-wrapper').append($itemContainer);
// Pass the container to the api function.
setStatus($this, processStatus, $itemContainer); //processStatus is function defined below
});
});
}
function setStatus(statusObject, callbackProcessStatus, $container){
FB.api("/{personId}?fields=id,link,name,picture",
function (response) {
var html = /* Generates html based from statusObject and response */
// Pass the item place holder/container to the processing procedure.
callbackProcessStatus(html, $container);
});
}
function processStatus(html, $container){
$container.append(html);
}
I have a chain of events set in .then statements. I wish I understood a way to use .when() in this case. This function is called on a ngn-click. The issue is that I have to click twice for it to go through. The $rootScope.csa has data going into the function that is used in the .then( functions ). I when in inspect in the chrome debugger the and step through everything works fine I believe it is because the debugger is slowing down the application and allowing it to keep up with its self. Other wise when I go through with out the debugger it goes so fast that it takes two clicks for $rootScope.csa.section.data to be populated for the next function to work as expected. The first two then statement functions are services that are wrapped in a promise and $timeout on there end and the $timeouts do not seem to be delaying the process. I have looked over q.derer() many times but cannot wrap my head around how it would be implemented in this case. Any help or information to get to the needs that I am looking for would ne appreciated.
audit.LockData('section', $scope.csa.ID, user.ID, $scope.csa.Revision)
.then($rootScope.csa = audit.RecordsCheck($rootScope.csa)) // gets data and pupulates it to the $rootscope.csa.section.data
.then($rootScope.csa = audit.GetInstance($rootScope.csa), function(){ return $rootScope.csa}) // gets ID(s) from $rootScope.csa.section.data.instances.ID(s) and populates them to the $rootScope.csa.section.instances
.then(function() {if($rootScope.csa.section.data) $location.path('/Update')})
.then(function() {if($rootScope.csa.section.data) $rootScope.$broadcast('root_updated')
});
You always need to pass a callback function to then, not some call result (even if it is a promise). I have not wrapped my head around what these functions do, but try
audit.LockData('section', $scope.csa.ID, user.ID, $scope.csa.Revision)
.then(function(lockResult) {
return audit.RecordsCheck($rootScope.csa)) // gets data and pupulates it to the $rootscope.csa.section.data
})
.then(function(checkResult) {
return audit.GetInstance($rootScope.csa) // gets ID(s) from $rootScope.csa.section.data.instances.ID(s) and populates them to the $rootScope.csa.section.instances
})
.then(function(instance) {
if ($rootScope.csa.section.data) {
$location.path('/Update')
$rootScope.$broadcast('root_updated')
}
});