How to Wait for Multiple ngResource $resource Queries to Resolve - angularjs

I have these ngResource queries in my controller:
Ages.query(function(ages){
$scope.ages = ages;
});
Skinissues.query(function(skinissues){
$scope.skinissues = skinissues;
});
Skintypes.query(function(skintypes){
$scope.skintypes = skintypes;
});
Activities.query(function(activities){
$scope.activities = activities;
});
In the same controller in findOne function:
$scope.findOne = function() {
Products.get({
productId: $routeParams.productId
}, function(product) {
for (var i = 0; i < product.ages.length; i ++){
for (var j = 0; j < $scope.ages.length; j ++){
if (product.ages[i].id == $scope.ages[j].id){
$scope.ages[j]['ticked'] = true;
}
}
}
for (var i = 0; i < product.activities.length; i ++){
for (var j = 0; j < $scope.activities.length; j ++){
if (product.activities[i].id == $scope.activities[j].id){
$scope.activities[i]['ticked'] = true;
}
}
}
for (var i = 0; i < product.skintypes.length; i ++){
for (var j = 0; j < $scope.skintypes.length; j ++){
if (product.skintypes[i].id == $scope.skintypes[j].id){
$scope.skintypes[i]['ticked'] = true;
}
}
}
for (var i = 0; i < product.skinissues.length; i ++){
for (var j = 0; j < $scope.skinissues.length; j ++){
if (product.skinissues[i].id == $scope.skinissues[j].id){
$scope.skinissues[i]['ticked'] = true;
}
}
}
for (var i = 0; i < $scope.parents.length; i ++){
if ($scope.parents[i].id == product.parent.id){
$scope.parents[i]['ticked'] = true;
}
}
console.log('Products', product);
$scope.product = product;
});
};
This code, sometimes, it works, sometimes it doesn't, because in the findOne function, sometimes, the $scope.ages $scope.skinissues $scope.skintypes or $scope.activities is undefined.
This happens because their queries haven't finished yet.
What can I do to solve this problem?
Please help. Thanks.

Use $q.all to resolve the $resource promises.
angular.module('mean.variables')
.factory('Variables', function($q, Products, Ages, Activities, Skinissues, Skintypes, _){
return {
get: function(){
var promiseHash = {};
promiseHash.ages = Ages.query().$promise;
promiseHash.skinissues = Skinissues.query().$promise;
promiseHash.skintypes = Skintypes.query().$promise;
promiseHash.activities = Activities.query().$promise;
promiseHash.parents = Products.query().$promise;
return $q.all(promiseHash);
}
}
});
The above example function returns a promise that either resolves sucessfully to a hash of the query objects or resolves rejected with the first rejected response object.
The advantage of using $q.all() instead of $q.defer is that the promise chains aren't broken and error responses are retained for clients of the factory.
From the Docs:
The Resource instances and collections have these additional properties:
$promise: the promise of the original server interaction that created this instance or collection.
On success, the promise is resolved with the same resource instance or collection object, updated with data from server. This makes it easy to use in resolve section of $routeProvider.when() to defer view rendering until the resource(s) are loaded.
On failure, the promise is rejected with the http response object, without the resource property.
If an interceptor object was provided, the promise will instead be resolved with the value returned by the interceptor.
--AngularJS ngResource API Reference
all(promises);
Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
Parameters
An array or hash of promises.
Returns
Returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
--AngularJS $q Service API Reference -- $q.all

Thanks guy, I've came up with this service by using setInterval to check if all variable are defined.
angular.module('mean.variables')
.factory('Variables', ['$q', 'Products', 'Ages', 'Activities', 'Skinissues', 'Skintypes', '_', function($q, Products, Ages, Activities, Skinissues, Skintypes, _){
return {
get: function(){
var variables = {};
var defer = $q.defer();
Ages.query(function(res){
variables.ages = res;
});
Skinissues.query(function(res){
variables.skinissues = res;
});
Skintypes.query(function(res){
variables.skintypes = res;
});
Activities.query(function(res){
variables.activities = res;
});
Products.query(function(res){
variables.parents = res;
});
var timer = setInterval(function(){
if (variables.ages && variables.activities && variables.skinissues && variables.skintypes && variables.parents){
defer.resolve(variables);
clearInterval(timer);
}
}, 50);
return defer.promise;
}
}
}])

Related

Angular http call within an http call

I set up a function in my service to return a list of servers/hosts that are related to a certain application. For front end purposes I have been trying to assign the host a color depending on how many services on that host are running okay/warning/critical. To implement this I am first doing an api call to get all the hosts related to that application and then I loop through the returned hostlist and do another api call to get services.
My issue is that they are resolving in the correct order so my Data2 variable is returning "undefined". How do I get it to resolve within the first for loop so I can assign a status color to each host?
Is there a better way to implement this?
Here is the function I have defined in my service.
// Function to get Servers and all their information **********************************
service.getHosts = function(AppName){
var HostList = [];
//intial http call to get the correct hostlist associated with the selected application ************
var promise = $http.get('http://localhost:9000/App/' + AppName);
promise.then(function(response){
var Data = response.data.recordset;
//Looping through each host is the recordset to push them into the HostList[] **********************
for (i = 0; i <= Data.length -1; i++){
//variables for the loop
var StatusColor = '';
var StatusTextColor = '';
var count = 0;
//another http call to get the services for each host in the Hostlist ******************************
$http.get('http://localhost:9000/Service/' + Data[i].HostName)
.then(function(response){
var Data2 = response.recordset;
//looping through the services to see if any of the services have status other than ok (shortstatus != 0) ********
for(i = 0; i<= Data2.length-1; i++){
if(Data2[i].ShortStatus != 0){
count = count + 1;
}
}
//Assigning the status color for each host depending on how many services are not ok (either warning or critical) *************
if (count == 0){
StatusColor ='rgb(255,152,0)';
StatusTextColor = 'black';
}else if (count == 1){
StatusColor ='rgb(255,152,0)';
StatusTextColor = 'white';
}else{
StatusColor = 'rgb(244,67,54)';
StatusTextColor = 'white';
}
//Pushing host information and status color to the HostList **********************
HostList.push({
"address":Data[i].Address,
"hostname":Data[i].HostName.split('.')[0],
"fullhostname":Data[i].HostName,
"statuscolor":StatusColor,
// "textcolor":'black'
})
});
}
})
return HostList;
};
Any help is greatly appreciated or any suggests of a simpler or more elegant way would be awesome.
Use $q.all and promise chaining
service.getHosts = function (AppName) {
//returns a http promise
return $http.get('http://localhost:9000/App/' + AppName)
.then(function (response) {
var Data = response.data.recordset;
//makes an array of $http promises
var promises = Data.map(function (dt) {
return $http.get('http://localhost:9000/Service/' + dt.HostName)
});
//executes all the promises in the array simultaneously and returns a single promise object
//whose result(response.data) will be an array of all the responses from the promises in the array
return $q.all(promises);
})
};
//Call the service method
service.getHosts("app_name_here")
.then(function(response){
//response.data will have an array of responses for all the inner $http.get calls
//you wont still be able to return the data because everything is asynchronous
//Populate your data in the $scope here after data massaging
})
Promises can be chained by using a return statement:
$http.get(url).then(function(response) {
return $http.get(url2);
}).then(function(response2) {
return $http.get(url3);
}).then(function(response3) {
//...
});
For more information, see
AngularJS $q Service API Reference - Chaining Promises
You're Missing the Point of Promises

AngularJS - getting asynchronous data back from a factory to use in a controller

The issue I'm having is that I'm trying to get data from my factory to my controller in time for the controller to access that data. Currently, when I console log out the data, I get an empty object, but if I examine the data further I get the whole "value was snapshotted, but here it is live" in Chrome.
Here's my Factory, called DataService:
var data = {};
var firstPillarData = {};
var secondPillarData = {};
var thirdPillarData = {};
firstPillarData.ourArray = [];
secondPillarData.ourArray = [];
thirdPillarData.ourArray = [];
function userRetrievalSuccess(response){
console.log('userRetrievalSuccess', response.data);
data.users = response.data;
console.log('data.users is', data.users);
console.log('and the data object is', data);
for(var i = 0; i < data.users.length; i++){
if(data.users[i].initiatives != null){
console.log("we have initiatives for this user", data.users[i]);
for(var j = 0; j < data.users[i].initiatives.length; j++){
switch(data.users[i].initiatives[j].pillar){
case 1:
firstPillarData.ourArray.push(data.users[i].initiatives[j]);
break;
case 2:
secondPillarData.ourArray.push(data.users[i].initiatives[j]);
break;
case 3:
thirdPillarData.ourArray.push(data.users[i].initiatives[j]);
break;
default:
break;
}
}
}
}
data.firstPillarData = firstPillarData;
data.secondPillarData = secondPillarData;
data.thirdPillarData = thirdPillarData;
console.log("our data.firstPillarData is", data.firstPillarData);
console.log("our data.secondPillarData is", data.secondPillarData);
console.log("our data.thirdPillarData is", data.thirdPillarData);
return data;
}
function userRetrievalFail(){
console.log('error retrieving users');
}
function getAllUserData(){
$http.get('/kpi/allUsers/').then(userRetrievalSuccess, userRetrievalFail)
}
And here's where it's being called in my controller:
DataService.getAllUserData();
var data = DataService.data;
I thought that using the .then method on the $http.get would handle my issue, but it clearly isn't. What am I doing wrong?
It turns out that adding a second promise/.then on the controller side took care of my issues. But thanks to #JB-Nizet and #Ladmerc for the help!

$http in loop, wait the results

I have this :
var j = 0;
// myList.length = 3
while(j < self.myList.length - 1){
$http.post('myURL', self.myList[j].code).then(
function(response){
self.myList[j].plop = response.data;
}, function(){
// error
}
).then(
function(){
// with this j++, my web page is freezing
// j++;
}
);
// with this j++, only the 3rd element of myList have a "plop" element
//j++;
}
My problem is in comments :) for "j++".
If I remove the loop while and hardcoded the 3 step, it's working. But I don't know how to solve the issue with a loop. Do you have an idea ? Thanks
Synchronized solution according to OP's comment:
var promises = [];
for(var i=0;i<self.myList.length;i++)
promises.push($http.post(...));
$q.all(promises).then(function(results){
//All results available here
var data = results.map(result => result.data);
data.forEach((e, idx) => self.myList[idx] = e);
})
.catch(function(e){
//Handle error
});
Issue is
$http.post().then(function(){}, function(){}) is asynchronous function. So .then() method will be executed when loop is completed. This is the reason it always takes last value of j that is 2.
then() will come only one time with $http.post() so remove second .then()
Solution posted below:
var j = 0;
var i = 0;
// myList.length = 3
while(j < self.myList.length - 1){
$http.post('myURL', self.myList[j].code).then(function(response){
self.myList[i].plop = response.data;
i++;
}, function(){
// error
}
);
}

How to do paginated API calls until reaching the end?

I imagine this could be a pretty general problem, but in this case I'm using AngularJS and the SoundCloud API.
Here's the flow:
Call loadTracks()
loadTracks() should load the tracks of a SoundCloud user, 50 at a time, until the list runs out.
loadTracks() does this by calling another function, sc.getTracksByUser(id), which returns a promise
loadTracks() should update the variable $scope.tracks with each 50 track batch when it arrives
The SoundCloud API provides an option offset, so loading the batches is relatively easy. I think it's the promise that is tripping me up. Without the promise, the solution would be:
$scope.tracks = [];
var loadTracks = function() {
var page = -1,
done = false,
newTracks;
while (!done) {
newTracks = getFiftyTracks(page++);
for (var i = 0; i < newTracks.length; i++) {
$scope.tracks.push(newTracks[i]);
}
if (newTracks.length < 50) done = true;
}
}
Unfortunately, that line with getFiftyTracks in it is not how it works. The actual implementation (using a promise) is:
sc.getTracksByUser(id).then(function (response) {
for (var i = 0; i < response.length; i++) {
$scope.tracks.push(response[i]);
}
}
I'm guessing the solution to this is some sort of recursion, but I'm not sure.
You can do that in this way
sc.getTracksByUser(id).then(function (response) {
for (var i = 0; i < response.length; i++) {
$scope.tracks.push(response[i]);
}
// if response return 50 track call getTracksByUser again
if (response.length === 50) sc.getTracksByUser(id);
});

Chaining promises with $q in Angular

I am trying to chain promises so that doQuery(0) executes then doQuery(1), etc... sequentially until doQuery(9).
My problem is that i is always equals to 10 in the callback function.
doQuery(0) executes then doQuery(10).
How do I pass each value of i in the callback function?
var promise = doQuery(0);
for (var i = 1; i < 10; i++) {
promise = promise.then(function() {
doQuery(i);
});
};
Since you're using Angular.js, you should use it's bind function here:
var promise = doQuery(0);
for (var i = 1; i < 10; i++) {
promise = promise.then(angular.bind(null, doQuery, i));
}
Without relying on Angular.js, you could use a closure to make a copy of i for each callback function (rather than having them all share the single copy of i in the outer scope):
var promise = doQuery(0);
for (var i = 1; i < 10; i++) {
promise = promise.then(function(i){
return function(){
doQuery(i);
};
}(i));
}
In modern Javascript engines you can also use the native Function.prototype.bind:
var promise = doQuery(0);
for (var i = 1; i < 10; i++) {
promise = promise.then(doQuery.bind(null, i));
}
You need to return each to-be-chained promise from the then callback, otherwise it will likely fail. To pass the right i value to each callback, see JavaScript closure inside loops – simple practical example.
var promise = doQuery(0);
for (var i=0; i<10; i++) (function(ii) {
promise = promise.then(function() {
return doQuery(ii);
});
})(i);

Resources