I'm quite new to angular and http calls.
Then i read something about Promise to aync calls. And i cannot understand it at all.
So i need some help to know if im even going the right direction.
I'm programming an API for getting Videoes, and details about them (Views etc) with Youtube API v3.
But i seem to get an error with the getting the details since my array is empty all the time.
/*var promises = [];*/ // PROMISE
var videometrics;
var videodetails = [];
var deferred = $q.defer();
$http.get('https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=' + playlistId + '&key=AIzaSyDQv-WpATIWLinCB3H_sH4W1sKx7plyvRA')
.success(function (response) {
for (var i = 0; i < response.items.length; i++) {
var video = {
id: response.items[i].snippet.resourceId.videoId,
title: response.items[i].snippet.title
};
$scope.video.push(video)
}
/*console.log($scope.video)
var promises = [];*/
for (i = 0; i < $scope.video.length; i++) {
/*console.log("looping")
console.log($scope.video)
console.log("Vi henter fra id:")
console.log($scope.video[i].id)*/
$http.get('https://www.googleapis.com/youtube/v3/videos?part=statistics&id=' + $scope.video[i].id + '&key=AIzaSyDQv-WpATIWLinCB3H_sH4W1sKx7plyvRA')
.success(function (responsevideo) {
/*console.log($scope.video[i].id);*/
// console.log("we are in the metric loop")
// console.log($scope.video[i].id)
// console.log($scope.video[i].title)
// console.log(responsevideo)
videometrics = {
id: responsevideo.items[0].id,
views: responsevideo.items[0].statistics.viewCount,
likes: responsevideo.items[0].statistics.likeCount,
dislikes: responsevideo.items[0].statistics.dislikeCount,
favorites: responsevideo.items[0].statistics.favoriteCount,
comments: responsevideo.items[0].statistics.commentCount
};
videodetails.push(videometrics);
deferred.resolve(responsevideo);
/*detailsOnVideos = $scope.videometrics;*/
})
/* videodetails.push(videometrics);*/
}
/*promises.push(videodetails);*/ // PROMISE
console.log(videodetails);
/* console.log(promises);*/ //PROMISE LOGGER
/* console.log(videodetails);*/
console.log($scope.video);
console.log("")
pagetokenarr = response.nextPageToken;
console.log(pagetokenarr)
});
return deferred;
/*return $q.all(promises);*/ // PROMISE
as you see my first http get, is functionel, but the next one is not. And i cant understand why. But if i push my videodetails array into a promise array it works. And again. I donno why.
You don't need $q with $http. There's basically two ways (that I know of) to work with $http:
1) make a service, isolate $http there. Return the promise as such:
return $http.get({params...}).then(function(data) {
return data.result;
});
then in the controller you'd have:
SuperService.get(params...).then(function(data) { $scope.something = data; });
2) databinding! make a service, put the $http in there. But this time you bind the result to an object inside the service. In the service you also have a method to return that object.
$http.get({params...}).then(function(data) {
myObject = data.result;
});
Then in the controller:
$scope.spiderman = SuperService.getObj();
SuperService.get(params...);
When you call .get() AngularJS does its magic and updates $scope.spiderman (as soon as you get a response from the server, of course).
Relevant literature:
ngDocs
"We have a problem with promises" by Lawson
Here is a solution working in this plunker
Your code was pretty much working. The problem was that your console.log() was fired too early because it wasn't in a .success() or .then() of your promises. It was fired before the $http calls resolved.
I reworked a bit your code (specially theses for loops) to make it more readable.
Also you don't need to build your own promises till $http calls return promises
Here is the final code :
$http.get('https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=' + playlistId + '&key=AIzaSyDQv-WpATIWLinCB3H_sH4W1sKx7plyvRA')
.success(function (response) {
angular.forEach(response.items, function(item){
var video = {
id: item.snippet.resourceId.videoId,
title: item.snippet.title
};
$scope.video.push(video);
})
console.log($scope.video);
angular.forEach($scope.video, function(video){
httpcalls.push($http.get('https://www.googleapis.com/youtube/v3/videos?part=statistics&id=' + video.id + '&key=AIzaSyDQv-WpATIWLinCB3H_sH4W1sKx7plyvRA')
.success(function (responsevideo) {
videometrics = {
id: responsevideo.items[0].id,
views: responsevideo.items[0].statistics.viewCount,
likes: responsevideo.items[0].statistics.likeCount,
dislikes: responsevideo.items[0].statistics.dislikeCount,
favorites: responsevideo.items[0].statistics.favoriteCount,
comments: responsevideo.items[0].statistics.commentCount
};
$scope.videodetails.push(videometrics);
}));
});
pagetokenarr = response.nextPageToken;
$q.all(httpcalls).then(function(){
console.log($scope.videodetails);
})
});
Note that i push all the $http call into a collection (httpcalls) and wrap your console.log into a $q.all(httpcalls).then() function. This will wait until all the $http calls into the collection are resolved.
Hope it helped.
Your code seems to working fine. I have removed all but the essentials, added the variables that I needed as placeholders as I'm not sure of the playlistId etc. Here is the code that I have tested.
var app = angular.module('app', []);
app.controller('ctrl', function ($scope, $http) {
var videometrics;
$scope.videodetails = [];
var playlistId = "PL63F0C78739B09958";
$scope.video = [];
$http.get('https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=' + playlistId + '&key=AIzaSyDQv-WpATIWLinCB3H_sH4W1sKx7plyvRA')
.success(function (response) {
for (var i = 0; i < response.items.length; i++) {
var video = {
id: response.items[i].snippet.resourceId.videoId,
title: response.items[i].snippet.title
};
$scope.video.push(video);
}
for (i = 0; i < $scope.video.length; i++) {
$http.get('https://www.googleapis.com/youtube/v3/videos?part=statistics&id=' + $scope.video[i].id + '&key=AIzaSyDQv-WpATIWLinCB3H_sH4W1sKx7plyvRA')
.success(function (responsevideo) {
var videometrics = {
id: responsevideo.items[0].id,
views: responsevideo.items[0].statistics.viewCount,
likes: responsevideo.items[0].statistics.likeCount,
dislikes: responsevideo.items[0].statistics.dislikeCount,
favorites: responsevideo.items[0].statistics.favoriteCount,
comments: responsevideo.items[0].statistics.commentCount
};
$scope.videodetails.push(videometrics);
});
}
});
});
This will display in the browser using {{videodetails}} when it available on the scope.
If you need to work with the data as opposed to merely displaying it you will likely need to use a service as the other answer suggests.
Related
I got this array objects to be read:
These was my sample codes:
$scope.obj_qst_local_study_main = tbl_qst_local_study_main.all();
$scope.quesion_id_allocated = $scope.obj_qst_local_study_main[0];
$timeout(function(){
console.log('----------all objects----------');
console.log($scope.obj_qst_local_study_main);
console.log('-----------one object-----------');
console.log($scope.quesion_id_allocated);
},200);
When I used:
$scope.obj_qst_local_study_main[0];
The result was: undefined
My angularjs services:
.service('tbl_qst_local_study_main', function($cordovaSQLite, DATABASE_LOCAL_NAME){
var self = this;
var qst_local_study_main_array = [];
self.all = function() {
var db = $cordovaSQLite.openDB({name: DATABASE_LOCAL_NAME,location:'default'});
$cordovaSQLite.execute(db, "SELECT * FROM qst_local_study_main")
.then(function (res) {
console.log('--------Successfully read from qst_local_study_main---------');
for (var i = 0; i < res.rows.length; i++) {
qst_local_study_main_array.push(res.rows.item(i));
}
},
function (err) {
console.log(err);
});
return qst_local_study_main_array;
};
})
Your service should return a Promise. This is a super common case, because (don't be offended please) people do not understand how Promises work.
Please search the internet for an article, like this one: https://developers.google.com/web/fundamentals/getting-started/primers/promises
tl;dr Your service should return a Promise. In your case $cordovaSQLite.execute Then you can correctly handle the response by chaining thens. You also do not need the timeout. Using a timeout is super bad here!
tbl_qst_local_study_main.all()
.then(function(result) {
console.log(result);
})
I'm currently making a call to a service method to return player profiles within my Angular app. I then need to immediately 'massage' some of the returned data by utilizing the results from another service method call. I'm wondering whether I am structuring my controller correctly below, or if I should make my codesService call within the $promise resolution of my playersService call instead.
Note: The controller below is a simplified version of my actual controller. The actual controller involves 'massaging' over a dozen elements.
'use strict';
angular.module('gameApp')
.controller('ProfileController', ProfileController);
ProfileController.$inject = ['$routeParams', 'playersService', 'codesService'];
function ProfileController($routeParams, playersService, codesService) {
var vm = this;
var playerId = $routeParams.playerId;
var codes;
var getCodes = function() {
codesService.get().$promise.then(function(data) {
codes = data;
});
};
var getProfiles = function() {
playersService.getProfiles({
playerId: playerId
}).$promise.then(function(profiles) {
for (var i = 0; i < profiles.length; i++) {
profiles[i].name = codes.MSP[profiles[i].MSPkey].name;
}
vm.profiles = profiles;
});
};
var init = function() {
getCodes();
getProfiles();
};
init();
}
Your controller should be very simple and get directly and exactly what it needs.
.controller("ProfileController", function(profileService, $routeParams){
var vm = this;
var playerId = $routeParams.playerId;
profileService.getProfiles(playerId)
.then(function(profiles){
vm.profiles = profiles;
});
});
profileService should just return everything neatly tied and packaged.
It will need to use the other services (i.e. codesService, playersService) as dependencies. And also, as I mentioned in the comments (and a few others in their answers), you can invoke the calls to codesService and playersService in parallel, but you must wait for both to finish.
.factory("profileService", function($q, codeService, playersService){
var svc = {
getProfiles: function(playerId){
$q.all({
players: getPlayers(playerId),
codes: getCodes()
})
.then(function(data){
var players = data.players;
var codes = data.codes;
return DoMassaging(players, codes);
})
}
};
return svc;
});
Make sure that getPlayers() and getCodes() return their respective promises.
If a then method returns a promise then Angular will use the results of the second promise for any subsequent then()] calls.
But all you really have here is a situation where you need both promises before you can progress, and for that you need is $q.all()
(Also, just return a promise from your service methods. The indirection doesn't improve your code.)
$q.all([getCodes(), getProfiles()).then(function(data)) {
var codes = data[0];
var profiles = data[1];
}
If you don't like the array arithmetic you can split it into two phases by chaining your then() calls.
What you have done looks dangerous, because if for any reason codeService.get() takes longer than playersService.getProfiles({..}) then you'll get an error beacuse codes will be undefined when you try to access codes.MSP[profiles[i].MSPkey].name.
$q.all(promises)
$q.all (docs page) takes an array or hash of promises and returns its own promise which will only be resolved when all of the others are:
$q.all({
codes: codesService.get().$promise,
profiles: playersService.getProfiles({
playerId: playerId
}).$promise
})
.then( function (results) {
var profiles = results.profiles;
var codes = results.codes;
for (var i = 0; i < profiles.length; i++) {
profiles[i].name = codes.MSP[profiles[i].MSPkey].name;
}
vm.profiles = profiles;
});
I am having difficulties loading data from Firebase into my AngularJS factory and thus controllers.
Basically I have the following factory:
.factory('usersList', ['fbutil', function(fbutil) {
return {
all: function() {
return fbutil.syncArray('users');
},
get: function(userId) {
fbutil.syncArray('users').then( function(result) {
window.alert(result.length) // FIRST ALERT
var x = 0;
for (var i = 0, len = result.length; i < len; i++) {
if (result[i].uid == userId) {
window.alert("fount id") // SECOND ALERT
x = i;
} else {
window.alert("nope"); // THIRD ALERT
}
}
return result[x];
}) // then
} // get
} // return
}]); // usersList
And my controller looks like:
.controller('OverviewCtrl', ['$scope', 'fbutil', 'usersList', function($scope, fbutil, usersList) {
$scope.usersList = usersList.all();
$scope.testUser = usersList.get("simplelogin:29");
// OTHER CODE
};
}])
In my HTML file, when I call {{usersList}} then it produces the result:
[{"color":"#CC0066","email":"a#a.com","name":"Eva","uid":"simplelogin:27"},{"color":"#009933","email":"b#b.com","name":"Semko","uid":"simplelogin:28"},{"color":"#CC0066","email":"c#c.com","name":"Caroline","uid":"simplelogin:29"}]
But testUser does not load, just shows {{tesUser}} in the index file.
Does anyone know how to handle this correctly? Without using the then(), which in this example also does not work, I figured out from the first alert that the result.length equaled 0, which gave me the suggestion that I am dealing with asynchronous loading. That is why I am trying to handle it whit .then() but apparently it is not working.
To handle a promise in angularjs the best way it to use defer values, since it allows you to process it before returning the data while keeping everything non blocking. With $http I would process like this :
function get(id) {
var deferred = $q.defer();
var url = "anUrlwithid";
$http.get(url).success(function(data, status) {
logger.logDebug('Successful GET call to ' + url + '\n, results ' + status + ': ' + data);
deferred.resolve(function(data){
//do something to your data then return
});
}).error(function(data, status) {
logger.logDebug('Failed GET call to ' + url + '\n, results ' + status + ': ' + data);
deferred.reject(data);
});
return deferred.promise;
}
And to process it in the controller :
get(1).then(function(data){//update scope or do something with the data processed});
You should be able to use that with your fbutil since it returns a promise I think.
Hope it helps
More details on the Q module here : https://docs.angularjs.org/api/ng/service/$q
PS: the logger is one of my personal service, just use console.log instead
Following is the code in which I am trying to use promise to save data from an asynchronous call to a variable but its not working. I am new to promise as I serached and found promise helps in these cases but I am unable to apply, let me know what I am doing wrong here -
angular.module("app").controller("myCtrl", function($scope, $http, $q) {
var deferred = $q.defer();
var data = $http.get("/api/events").success(function(response){
deferred.resolve(response);
return deferred.promise;
// ALSO tried return response;
})
console.log("DATA--");
console.log(data);
});
EDIT -
I am trying to hit two APIS -
1) Create array of ids from 1st API hit.
2) Loop to the array for 2nd API hit on id basis.
3) Concatenate some data from array 1 and array2.
More specific case, which I am trying to do but found to use promise -
http://pastebin.com/ZEuRtKYW
I would do it as follows:
$http.get('data.json').success(function (result) {
$scope.result = result;
}).then(function (data) {
var result = data.data.key;
var promises = result.map(function (val) {
var deffered = $q.defer();
$http({
method: 'GET',
url: 'data-' + val.id + '.json'
})
.success(function (x) {
deffered.resolve(x);
})
.error(function () {
deffered.reject();
});
return deffered.promise;
});
$q.all(promises).then(function (result) {
$scope.resolvedData = result;
});
});
Map all the promises into a promises array based on the result of the first call. Inside this map function create a new promise and resolve it in the success function. Make sure you return the actual promise!
Afterwards you can get all the resolved data with $q.all(promises). This way your calls to the database are not limited to 2. You can do as much calls as you want based on the data retrieved in the first call.
Plunker
Edit: Not sure if you're able to modify the service, but it would be better if you can achieve this with only one call. For instance:
get '/api/events' --> returns just the events
get '/api/events?includeDetail=true' --> returns the events + the details of an event
When you get response in asynchronous call, store it in scope variable. Then you can access that scope variable anywhere inside controller.
angular.module("app").controller("myCtrl", function($scope, $http) {
$http.get("/api/events").success(function(response){
$scope.response = response;
})
});
I think this can be done somthing like as:
angular.module("app").controller("myCtrl", function($scope, $http, $q) {
var ids = [],
q1 = $q.defer(),
q2 = $q.defer();
d1 = $http.get("/api/events").success(function(data){
q1.resolve(data);
angular.forEach(data, function(id) {
ids.push(id);
});
}).error(function(err){
q1.reject("Ooops!!")
});
var promises = [];
angular.forEach(ids, function(id) {
var promise = $http.get("/api/events", {id:id})
.success(function(data){
q2.resolve(data);
}).error(function(err){
q2.reject("Ooops!!")
});
promises.push(promise);
});
$q.all(promises)
.then(function(values) {
console.log(values);
});
});
I have a service SQLService on my PhoneGap/AngularJS app that runs when the app is loading. It iterates through a long array of Guidelines, and makes a DB transaction for each one. How can I signal that the final transaction has been completed?
What I want to have happen is something like:
In the controller, call `SQLService.ParseJSON`
ParseJSON calls `generateIntersectionSnippets`
`generateIntersectionSnippets` makes multiple calls to `getKeywordInCategory``
When the final call to getKeywordInCategory is called, resolve the whole chain
SQLService.ParseJSON is complete, fire `.then`
I really don't understand how to combine the multiple asynchronous calls here. ParseJSON returns a promise which will be resolved when generateIntersectionSnippets() is completed, but generateIntersectionSnippets() makes multiple calls to getKeywordInCategory which also returns promises.
Here's a simplified version of what's not working (apologies for any misplaced brackets, this is very stripped down).
What I want to happen is for $scope.ready = 2 to run at the completion of all of the transactions. Right now, it runs as soon as the program has looped through generateIntersectionSnippets once.
in the controller:
SQLService.parseJSON().then(function(d) {
console.log("finished parsing JSON")
$scope.ready = 2;
});
Service:
.factory('SQLService', ['$q',
function ($q) {
function parseJSON() {
var deferred = $q.defer();
function generateIntersectionSnippets(guideline, index) {
var snippet_self, snippet_other;
for (var i = 0; i < guideline.intersections.length; i++) {
snippet_self = getKeywordInCategory(guideline.line_id, snippets.keyword).then(function() {
//Should something go here?
});
snippet_other = getKeywordInCategory(guideline.intersections[i].line_id, snippets.keyword).then(function() {
//Should something go here?
});
}
}
deferred.resolve(); //Is fired before the transactions above are complete
}
generateIntersectionSnippets();
return deferred.promise;
} //End ParseJSON
function getKeywordInCategory(keyword, category) {
var deferred = $q.defer();
var query = "SELECT category, id, chapter, header, snippet(guidelines, '<b>', '</b>', '...', '-1', '-24' ) AS snip FROM guidelines WHERE content MATCH '" + keyword + "' AND id='" + category + "';",
results = [];
db.transaction(function(transaction) {
transaction.executeSql(query, [],
function(transaction, result) {
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
var row = result.rows.item(i);
results.push(row);
}
}
},defaultErrorHandler);
deferred.resolve(responses);
},defaultErrorHandler,defaultNullHandler);
return deferred.promise;
}
return {
parseJSON : parseJSON
};
}]);
I'd appreciate any guidance on what the correct model is to doing a chain of promises that includes an iteration across multiple async transactions- I know that how I have it right now is not correct at all.
You can use $q.all() to wait for a list of promises to be resolved.
function parseJSON() {
var deferred = $q.defer();
var promiseList = [];
for (var i = 0; i < guideline.intersections.length; i++) {
promiseList.push(getKeywordInCategory(guideline.line_id, snippets.keyword));
promiseList.push(getKeywordInCategory(guideline.intersections[i].line_id, snippets.keyword));
}
$q.all(promiseList).then(function() {
deferred.resolve();
});
return deferred.promise;
} //End ParseJSON