How to do paginated API calls until reaching the end? - angularjs

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

Related

AngularJS - Delete check box is not working correctly and only deleting one at a time

I've written out a block of code that allows the user to check or uncheck entities that will be added or removed via web services. My add function seems to be working correctly and provides the ability to add multiple entities. However, my delete function isn't working the same. It doesn't delete each time, and can only delete one at a time. I'm struggling since the code is effectively the same as the add, so I don't know if the issue is AngularJS related or perhaps my web service isn't working correctly.
Edit: I've actually noticed that the for loop goes through it all but doesn't select the correct id, it always starts from the first one.
var toDeleteService = [];
for (var i = 0; i < $scope.siteServices.length; i++) {
if ($scope.siteServices[i].chosen != $scope.siteServices[i].origChosen) {
if ($scope.siteServices[i].chosen == true) {
toAddService.push(i);
}
else {
toDeleteService.push(i);
}
}
}
if (toDeleteService.length > 0) {
var deleteRequest = {};
deleteRequest.services = [];
for (var i = 0; i < toDeleteService.length; i++) {
var parentServiceName = $scope.siteServices[i].parentServiceName;
var j = 0;
for (; j < deleteRequest.services.length; j++) {
if (deleteRequest.services[j].parentServiceName == parentServiceName) {
break;
}
}
if (j == deleteRequest.services.length) {
deleteRequest.services[j] = {};
deleteRequest.services[j].parentServiceName = parentServiceName;
deleteRequest.services[j].subservices = [];
}
var service = {};
service.serviceId = $scope.siteServices[i].serviceId;
deleteRequest.services[j].subservices.push(service);
}
var deleteUrl = "api/sites/" + $scope.targetEntity.siteId + "/services/" + service.serviceId;
$http.delete(deleteUrl)
.then(function (response) {
});
}
As I understood it you are trying to remove siteServices based by numbers stored in var toDeleteServices = [] so you need to access those numbers by its index. but in service.serviceId = $scope.siteServices[i].serviceId; you are using i instead.
service.serviceId = $scope.siteServices[toDeleteServices[i]].serviceId; as you need actual number of the service to delete.
If I understood your code correctly.

Loading of paginated data from API

I am working on a system that currently requires me to load all items from an API.
The API is built with pagination feature in it. I keep calling the API a number of times and $http.get cursing the system not to respond. For example, once I load the page that needs to call the API many times (like 50 to 80 times depending on the number of pages), for a few minutes anything I do won't respond until the calling of the API is almost finished. I already tried a lot of ways but it won't work.
$scope.loadAllPagedItems = function (category_uuid, max_pageitem, item_perpage) {
for (var a = 0 ; a < max_pageitem; a++) {
itemResource.findItems(category_uuid, item_perpage, a).then(function (response) {
if (response.data.data.length > 0) {
for (var a = 2 ; a < $scope.categories.length; a++) {
if ($scope.categories[a][0].category_uuid == response.data.data[0].category_uuid) {
for (var j = 0; j < response.data.data.length; j++) {
$scope.categories[a][0].data.push(response.data.data[j]);
}
}
}
}
}, function (error) {
console.log(error);
})
}
}
Is there any way I can do this better?
Sequentially Retrieving Paginated Data from an Asynchronous API
$scope.loadAllPagedItems = function(category_uuid, max_pageitem, item_perpage) {
var promise = $q.when([]);
for (let a = 0 ; a < max_pageitem; a++) {
promise = promise
.then(function(dataArray) {
var p = itemResource.findItems(category_uuid, item_perpage, a);
p = p.then(function (response) {
return dataArray.concat(response.data.data);
});
return p;
});
};
return promise;
};
The above example, executes XHRs sequentially and uses the array concat method to merge the resolved arrays into a single array.
Usage:
$scope.loadAllPagedItems(category, pages, itemsPerPages)
.then(function(finalArray) {
console.log(finalArray);
}).catch(function(response) {
console.log("ERROR ",response.status);
throw response;
});

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

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

Angular sequential $http requests of multiple elements in list?

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.

Resources