Angular sequential $http requests of multiple elements in list? - angularjs

Say I have a list:
var mylist = ["a","b","c","d"];
I want to request data for each one of these like so and get the responses back in the same order.
var comeback = [];
getMyData()
function getMyData() {
for (int i = 0; i < mylist.length; i++) {
$http.get("http://myurl/" + mylist[i]).success(function(data) {
results.append(data);
});
}
}
How can I make sure that the "comeback" list has all the responses based on "a", "b", etc. in the same order? What is the best way to write this?

Chaining the promises will make them execute in series. Something like:
var results = [];
getMyData(0);
function getMyData(i) {
return $http.get("http://myurl/" + mylist[i]).success(function(data) {
results.push(data);
i++;
if(i < mylist.length) {
getMyData(i);
}
});
}
Note: If you want to do more advanced validation and error checking, you would need to use $q.

Related

Iterating inside $http.get

I've this block of code which displays 20 items per request.
.controller('MovieController',function ($scope,$http) {
$scope.movies = $http.get(https://api.themoviedb.org/3/movie/popular&page=1)
.then(
function (response) {
$scope.movies = response.data.results;
}
);
})
Instead of page=1, if i put page=2, I'll get another set of 20 items and so on. How to iterate inside the $http.get if I want more than 1 get request in a single page? I want to display the 2nd 20 items after displaying the 1st 20.
There is the classic way of chaining callback functions together to achieve the desired result. Or there is the better way, which uses $q.all().You should use $q.all to send multiple http requests at once
var reqAr = [];
for (var i = 0; i <= 1; i++) {
reqAr.push($http.get("https://api.themoviedb.org/3/movie/popular&page=" + i))
}
$q.all(reqAr).then(function(response) {
console.log(response[0].data) //page 1 content
console.log(response[1].data) //page 2 content
for(var j = 0; j < response.length; j++) {
$scope.movies = $scope.movies.concat(response[j].data.results);
}
})

FIlter in controller with multiple values

I have 3 switches (checkboxes, they return true or false) to filter in a list.
The list:
vm.products = Product.query();
In my controller, i want to filter vm.products, everytime one of the switchboxes/checkboxes get changed.
All i got so far, is a none working, filter argument:
vm.products = $filter('filter')('id', 1);
The parameter 'filter' - sems like its pointing at a directive? Do i have to do that? And what would be the best way of making a dynamic filter function/builder, when there is multiple values to check on?
I found a way using a custom filter;
.filter("myFilter", function(){
return function(products, productTypes){
var selectedProducts = [];
for (i = 0; i < products.length; i++) {
if (productTypes.indexOf(products[i].productTypeId.toString()) > -1) {
selectedProducts.push(incidents[i]);
}
}
return selectedProducts;
};
})
I then use this in my controller to call the filter function;
vm.filterTypeUpdated = function ($event, typeId) {
var productTypesId = "";
if (vm.filterType1)
productTypesId += "1,";
if (vm.filterType2)
productTypesId += "2,";
if (vm.filterType3)
productTypesId += "3,";
vm.productsRoot = $filter('myFilter')(vm.products, productTypesId);
}
I don't feel like this is the cleanest way of doing it, but it works. If there are any inputs on optimizing this, i am all ears :-)

In Angular, what's the best way to persist data through an asynchronous call?

I have a controller with a for loop that make's HEAD requests for an array of URLs to check if the file exists. When I get the response from the HEAD request, i need the index number from the array that the request was based on.
var allFiles = [], files = [];
allFiles.push({"url":"http://www.example.com/foo","source":"source1"});
allFiles.push({"url":"http://www.example.com/bar","source":"home"});
allFiles.push({"url":"http://www.example.com/wtf","source":"outer space"});
for(var i=0,len=allFiles.length;i<len;i++) {
$http.head(allFiles[i].url).then(function(response) {
files.push(allFiles[VALUE_OF_i_AT_TIME_OF_REQUEST]);
}
}
EDIT:
Because it is an asynchronous call, I cannot use i in place of VALUE_OF_i_AT_TIME_OF_REQUEST. Doing that results in i always being equal to len-1
I guess I can send the index number as data with the request and retrieve it from the response but for some reason that seems hack-ish to me.
Is there a better way?
You can do this with a function closure
for (var i = 0, len = allFiles.length; i < len; i++) {
function sendRequest(index) {
$http.head(allFiles[index].url).then(function (response) {
files.push(allFiles[index]);
});
}
sendRequest(i);
}
I may be oversimplifying this (asynchronous code is still tricky to me), but could you set i to a new local variable j on each loop then reference j instead of i in files.push(allFiles[j]):
var allFiles = [], files = [];
allFiles.push({"url":"http://www.example.com/foo","source":"source1"});
allFiles.push({"url":"http://www.example.com/bar","source":"home"});
allFiles.push({"url":"http://www.example.com/wtf","source":"outer space"});
for(var i = 0, len = allFiles.length; i < len; i++) {
var j = i;
$http.head(allFiles[i].url).then(function(response) {
files.push(allFiles[j]);
}
}
I did something similar to #rob's suggestion and it seems to be doing the trick.
var allFiles = [], files = [];
allFiles.push({"url":"http://www.example.com/foo","source":"source1"});
allFiles.push({"url":"http://www.example.com/bar","source":"home"});
allFiles.push({"url":"http://www.example.com/wtf","source":"outer space"});
for(var i=0,len=allFiles.length;i<len;i++) {
(function(i) {
$http.head(allFiles[i].url).then(function(response) {
files.push(allFiles[i]);
}
})(i);
}

AngularJS multiple concurrent chained $resource calls

I have a need to make multiple concurrent calls to an Angular resource, and chain some actions with the $promise api.
I define a resource like this
myServicesModule.factory('MyResource', ['$resource', 'SETTINGS', function($resource, SETTINGS) {
return $resource(SETTINGS.serverUrl + '/myResource/:id', { },
{
get: { method: "get", url: SETTINGS.serverUrl + '/myResource/show/:id' },
}
);
}]);
My controller needs to retrieve multiple records, and take actions on each one when the record is ready. I am having trouble passing values to the then() closure.
When I do this:
for (var i = 0; i < 3; i++) {
MyResource.get({id: i}).$promise.then(function(item) { console.log(i); });
}
The output is "2, 2, 2".
This code results in the desired output of "0, 1, 2" (order varies depending on when each resource call completes), but this is an ugly solution.
for (var i = 0; i < 3; i++) {
var closure = function(i) {
return function(item) { console.log(i); console.log(item); }
}
UwgCarrier.get({id: i}).$promise.then( closure(i) );
}
Why does the first code snippet return "2, 2, 2" ?
Is there a cleaner way to solve this problem?
It's a matter of closures. Just wrap your closure in another one.
You can make a workaround with a call to an immediate function, that would look like :
for (var i = 0; i < 3; i++) {
(function(c) {
UwgCarrier.get({id: c}).$promise.then( console.log(c); );
})(i);
}
In my example I have replaced "i" by "c" into the closure to make things clear. Like so, the immediate function invoke "i" with its current value within the loop process.
I don't think that there is a better way to do this as it's a javascript concern.
EDIT :
As ES6 is coming soon, you can use the "let" keyword to achieve the same goal without wrapping your inner loop in a closure, because "let" block-scoped your variable.
for (let i = 0; i < 3; i++) {
UwgCarrier.get({id: i}).$promise.then( console.log(i); );
}

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);
});

Resources