Angular Factory: Implementing getAll function to consume REST Api - javascript

I have a Angular.JS factory that pulls information from a REST Api.
The REST Api is called via
/api/getSsls/1
where 1 is the page number. The API returns a json object with the first ten items as well as information about how many pages/items there are in total.
I want to write a factory method, that gets all items from the api and walks
thru all pages.
This is what i tried:
app.factory('Ssls', function ($routeParams,$http) {
allSsls = [];
return {
list:
function (page, callback) {
return $http.get("/api/getSsls/" + page).success(callback);
},
listAll:
function (ssl, callback) {
var TotalPages = ssl.paging.TotalItems/ssl.paging.PageSize;
TotalPages = Math.ceil(TotalPages);
console.log("TOTAL Pages:" + TotalPages);
for(var i = 1; i < TotalPages; i++ ) {
this.list(i,this.processListAll(ssl));
// using this as callback above instead of processListAll(ssl) works and outputs all elements to the console
// function () {
// console.log(data.list[0]);
// });
};
},
processListAll:
function (data) {
for( var j = 0; j < data.list.length; j++){
console.log(data.list[j]);
allSsls.push(data.list[j]);
}
}
I then call this factory method from the controller:
Ssls.list("1",function(data) {
var list = Ssls.listAll(data);
console.log("ALL:" + list);
});
I have a few problems:
allSsls (as well as list in the controller) seems to be emtpy, probably somethings wrong with the scope of this variables?
processListAll seems to iterate thru the data set of the first page, probably something with the callback and parameter given to the callback is wrong
( this.list(i,this.processListAll(ssl));)
I'm new to stackoverflow and this is my first question. Thank you for your help!

You're using Ssls.list() with a callback inside your controller, there is no need to return the $http promise from your service, however your main problem is that you're invoking the callback incorrectly. When you execute the callback in your service, you don't pass the data to it! Do this instead:
list:function (page, callback) {
$http.get("/api/getSsls/" + page).success(function(data){
callback(data);
});
}

Related

How to call one HTTP Call inside another HTTP Call in a for loop in AngularJS?

I am working on an AngularJS Application.
I have the following array:
$scope.fruits = [
{url1: 'appleColor', url2: 'appleDetail'},
{url1: 'orangeColor', url2: 'orangeDetail'},
{url1: 'grapesColor', url2: 'grapesDetail'},
];
Now, I am calling HTTP GET requests like this:
for(var i = 0; i < $scope.fruits.length; i++){
var fruit = $scope.fruits[i];
getFruitColor(fruit.url1).then(function(color){
getFruitDetail(fruit.url2).then(function(detail){
console.log("color is "+ color);
console.log("detail is "+ detail);
}):
});
}
function getFruitColor(url){
return $http({
method: 'GET', url: url, params: {} }).then(getFruitComplete, getFruitFailed);
}
function getFruitDetail(url){
return $http({ method: 'GET', url: url, params: {} }).then(getFruitDataComplete, getFruitDataFailed);
}
function getFruitDataComplete(response) {
return response.data;
}
function getFruitDataFailed(error) {
$log.error('Failed to get fruit data - ' + error.data);
}
function getFruitComplete(response) {
return response.data;
}
function getFruitFailed(error) {
$log.error('Failed to get fruit- ' + error.data);
}
Now, since all of these calls would be asynchronous, I expected these calls in NETWORK Tab like this (order of these calls can be different because of asynchronous nature):
getFruitColor('appleColor')
getFruitColor('orangeColor')
getFruitColor('grapesColor')
getFruitDetail('appleDetail')
getFruitDetail('orangeDetail')
getFruitDetail('grapesDetail')
But what I am actually seeing in NETWORK Tab is this:
getFruitColor('appleColor')
getFruitColor('orangeColor')
getFruitColor('grapesColor')
getFruitDetail('grapesDetail')
getFruitDetail('grapesDetail')
getFruitDetail('grapesDetail')
I am a beginner in AngularJS and Javascript and I don't understand what is the issue here and why in the inner HTTP Call, url2 of last element of fruits array is going on for every element in loop.
Can anyone please explain why this behavior is happening here?
And what I should do to achieve the desired result?
try to use let (or const) instead of var for this assignment: var fruit = $scope.fruits[i];. something like that should do the trick:
for(var i = 0; i < $scope.fruits.length; i++) {
const fruit = $scope.fruits[i];
getFruitColor(fruit.url1).then(function(color) {
getFruitDetail(fruit.url2).then(function(detail) {
also consider using let i for the iteration (for(let i = ...).
note that var would be hoisted to the outer scope and each iteration would overwrite the same variable. all calls to getFruitDetail will use only the latest value of fruit which is why you see 3 calls with grapesDetail.
the key difference between var and let/const is that var is function scoped, while let/const are block scoped. this link could be of interest: https://dev.to/sarah_chima/var-let-and-const--whats-the-difference-69e (or a google search for the difference between var/let/const)

Run function after another function completes JavaScript and JQuery

I need a little help. I'm trying to run my second function "likeLinks();" but only after my first function "getLikeURLs();" is finished. This is because my 2nd function relies on the links Array to execute. It seems like they are trying to run at the same time.
Any help would be appreciated.
var links = [];
var url = '/' + window.location.pathname.split('/')[1] + '/' + window.location.pathname.split('/')[2] + '/'
getLikeURLs();
likeLinks();
function getLikeURLs() {
for (i = 1; i < parseInt(document.getElementsByClassName('PageNav')[0].getAttribute('data-last')) + 2; i++) {
var link = $.get(url + 'page-' + i, function(data) {
//gets the like links from current page
$(data).find('a[class="LikeLink item control like"]').each(function() {
links.push($(this).attr('href')); // Puts the links in the Array
});
});
}
}
function likeLinks() {
for (t = 0; t <= links.length; t++) {
var token = document.getElementsByName('_xfToken')[0].getAttribute('value')
$.post(links[t], {
_xfToken: token,
_xfNoRedirect: 1,
_xfResponseType: 'json'
}, function(data) {});
}
}
The link variables are actually jQuery deferred objects - store them in an array and then you can use $.when() to create a mew deferred object that only resolves when all of the previous $.get() operations have completed:
function getLikeURLs(url) { // NB: parameter, not global
var defs = [], links = []; // NB: links no longer global
for (...) {
var link = $.get(...);
defs.push(link);
}
// wait for previous `$.get` to finish, and when they have create a new
// deferred object that will return the entire array of links
return $.when.apply($, defs).then(function() { return links; });
}
Then, to start the chain of functions:
getLikeURLs(url).then(likeLinks);
Note that likeLinks will now be passed the array of links instead of accessing it from the global state. That function should also be rewritten to allow you to wait for its $.post calls to complete, too:
function likeLinks(links) {
// loop invariant - take it outside the loop
var token = document.getElementsByName('_xfToken')[0].getAttribute('value');
// create array of deferreds, one for each link
var defs = links.map(function(link) {
return $.post(link, {
_xfToken: token,
_xfNoRedirect: 1,
_xfResponseType: 'json'
});
});
// and another for when they're all done
return $.when.apply($, defs);
}
p.s. don't put that (relatively) expensive parseInt(document.getAttribute(...)) expression within the for statement - it'll cause it to be evaluated every iteration. Calculate it once outside the loop and store it in a variable. There's a few other places where you're repeating calls unnecessarily, e.g. window.location.pathname.split()
EDIT: My answer discusses the issue but see Alnitak answer for a much better solution.
The get in getLikeURLs and the put in likeLinks are both asynchronous. The calls to both of these function return immediately. When data is returned from the called server at some indeterminate time later, the callback functions are then called. The puts could return before the gets which would be a problem in your case. Also note that JavaScript is NOT multi-threaded so the two methods, getLikeURLs and likeLinks will never run at the same time. The callback functions, on the other hand, might be called at anytime later with no guarantee as to the call back order. For example, the 3rd get/put might return before the 1st get/put in your loops.
You could use $.ajax to specify that the gets and puts are synchronous but this is ill advised because the browser will hang if ANY get/put doesn't return in a reasonable amount of time (e.g. server is offline). Plus you don't have the "multi-tasking" benefit of sending out a lot of requests and having the various servers working at the same time. They would do so serially.
The trick is to simply call likeLinks form the callback function in getLikeURL. Your case is a little tricky because of the for loop but this should work:
var links = [];
var url = '/' + window.location.pathname.split('/')[1] + '/' + window.location.pathname.split('/')[2] + '/'
getLikeURLs();
//likeLinks(); // Don't call yet. Wait for gets to all return.
function getLikeURLs() {
var returnCount = 0; // Initialize a callback counter.
var count = parseInt(document.getElementsByClassName('PageNav')[0].getAttribute('data-last')) + 1;
for (i = 0; i < count; i++) {
var link = $.get(url + 'page-' + (i + 1), function(data) {
//gets the like links from current page
$(data).find('a[class="LikeLink item control like"]').each(function() {
links.push($(this).attr('href')); // Puts the links in the Array
});
// If all gets have returned, call likeLinks.
returnCount++;
if (returnCount === count) {
likeLinks();
}
});
}
}
function likeLinks() {
for (t = 0; t <= links.length; t++) {
var token = document.getElementsByName('_xfToken')[0].getAttribute('value')
$.post(links[t], {
_xfToken: token,
_xfNoRedirect: 1,
_xfResponseType: 'json'
}, function(data) {});
}
}

Array is empty outside the function, trying to fetch data from database, in Javascript

I'm trying to build an application with Ionic Framework and Javascript, and I want to fetch all the data from a table in an array, but when I try to access that array outside the function it's empty. I've read something that maybe the array gets populated after the console.log("length "+ $scope.cards.length); but I don't know how to solve this so I can use the array after that.
This is the function for fetching the data from the table(it works):
fetchCards: function(success) {
database.transaction(function(tx) {
tx.executeSql("SELECT cardId, name, chosen, filePath, found FROM Cards", [],
function (tx, results) {
console.log('success select ' + results);
cards = [];
for (i = 0; i < results.rows.length; i++) {
cards.push(results.rows.item(i));
}
success(cards);
},
function () {
console.log('error select');
});
});
}
This is where I try to access the array, in a controller:
.controller('kindergartenController', function($scope, $ionicSideMenuDelegate,service) {
//list with the difficulties
$scope.difficultyList=[
{text:"Easy", value:"easy"},
{text:"Medium", value:"medium"},
{text:"Hard", value:"hard"}];
$scope.data={difficulty:''};
//redirecting if it presses
$scope.goToAnimalsGame = function() {
if($scope.data.difficulty=='easy'){
window.location = "#/menu/animalseasy";
//in this array I want the results
$scope.cards=[];
game = new Game(1,"easyAnimalGame");
console.log("created a new game " + game.getName());
game.createMemoryGame();
console.log("difficulty " + $scope.data.difficulty);
randomNumbers=game.randomNumbers($scope.data.difficulty);
console.log('Random numbers are:');
for(i=0;i<randomNumbers.length;i++){
console.log(randomNumbers[i]);
}
//here is where I call the other function, to get data from database
service.fetchCards(function(cards) {
$scope.cards = cards;
//it shows the good length
console.log("length "+ $scope.cards.length);
});
//it shows it's empty
console.log("length "+ $scope.cards.length);
}
Any suggestions are helpful, thank you.
It will show empty because:
service.fetchCards
is Asynchronous, therefore inside that function where you define your anonymous callback, won't fire till the data is retrieved.
the console.log("length ".... outside of the function will be executed immediately after the function call fetchCards, but possibly before the callback where the array gets populated.
Unfortunately you can only deal with the populated array within the callback or from a function fired within the callback.
Here is a timeline of execution to aid:
service.fetchCards();
->
console.log("length "....) below the above function
->
anonymous callback (function (cards){}) inside the service.fetchCards()
If that makes sense.
The asynchronism of the service means that the callback you defined anonymously could fire at any time.
The solution:
service.fecthCards(function (data) {
// do your code
$scope.cards = cards;
//it shows the good length
console.log("length "+ $scope.cards.length);
// manipulate the array within this function or get it to call another function like so
nowContinue();
});
function nowContinue() {
// continue what ever it was that you wanted to do using the newly populated information
}
Because nowContinue is called with in the anonymous callback it will fire whenever the callback gets fired, and will execute after you have populated the controllers local variables with data.

Struggling with Q.js promises

I am sure I am missing something obvious but I can't seem to make heads or tails of this problem. I have a web page that is being driven by javascript. The bindings are being provided by Knockout.js, the data is coming down from the server using Breeze.js, I am using modules tied together with Require.js. My goal is to load the html, load the info from Breeze.js, and then apply the bindings to show the data to the user. All of these things appear to be happening correctly, just not in the correct order which is leading to weird binding errors. Now on to the code.
I have a function that gets called after the page loads
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(
applyBindings(vm)
);
}
This should call activate, wait for activate to finish, then apply bindings....but it appears to be calling activate, not waiting for it to finish and then runs applybindings.
activate -
function activate() {
logger.log('Frames Admin View Activated', null, 'frames', false);
return datacontext.getAllManufacturers(manufacturers)
.then(function () {
manufacturer(manufacturers()[0]);
}).then(function () {
datacontext.getModelsWithSizes(modelsWithSizes, manufacturers()[0].manufacturerID())
.then(datacontext.getTypes(types));
});
}
datacontext.getAllManufacturers -
var getAllManufacturers = function (manufacturerObservable) {
var query = entityQuery.from('Manufacturers')
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (manufacturerObservable) {
manufacturerObservable(data.results);
}
log('Retrieved [All Manufacturer] from remote data source',
data, true);
}
};
datacontext.getModelsWithSizes -
var getModelsWithSizes = function (modelsObservable, manufacturerId) {
var query = entityQuery.from('Models').where('manufactuerID', '==', manufacturerId)
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (modelsObservable) {
for (var i = 0; i < data.results.length; i++) {
datacontext.getSizes(data.results[i].sizes, data.results[i].modelID());
// add new size function
data.results[i].addNewSize = function () {
var newValue = createNewSize(this.modelID());
this.sizes.valueHasMutated();
return newValue;
};
}
modelsObservable(data.results);
}
log('Retrieved [Models With Sizes] from remote data source',
data, false);
}
};
Any help on why this promise isn't working would be appreciated, as would any process to figure it out so I can help myself the next time I run into this.
A common mistake when working with promises is instead of specifying a callback, you specify the value returned from a callback:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then( applyBindings(vm) );
}
Note that when the callback returns a regular truthy value (number, object, string), this should cause an exception. However, if the callback doesn't return anything or it returns a function, this can be tricky to locate.
To correct code should look like this:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(function() {
applyBindings(vm);
});
}

Variable scope. Use a closure?

I'm trying to grab all the URLs of my Facebook photos.
I first load the "albums" array with the album id's.
Then I loop through the albums and load the "pictures" array with the photos URLs.
(I see this in Chrome's JS debugger).
But when the code gets to the last statement ("return pictures"), "pictures" is empty.
How should I fix this?
I sense that I should use a closure, but not entirely sure how to best do that.
Thanks.
function getMyPhotos() {
FB.api('/me/albums', function(response) {
var data = response.data;
var albums = [];
var link;
var pictures = [];
// get selected albums id's
$.each(data, function(key, value) {
if ((value.name == 'Wall Photos')) {
albums.push(value.id);
}
});
console.log('albums');
console.log(albums);
// get the photos from those albums
$.each(albums, function(key, value) {
FB.api('/' + value + '/photos', function(resp) {
$.each(resp.data, function(k, val) {
link = val.images[3].source;
pictures.push(link);
});
});
});
console.log('pictures');
console.log(pictures);
return pictures;
});
}
You're thinking about your problem procedurally. However, this logic fails anytime you work with asynchronous requests. I expect what you originally tried to do looked something like this:
var pictures = getMyPhotos();
for (var i = 0; i < pictures.length; i++) {
// do something with each picture
}
But, that doesn't work since the value of 'pictures' is actually undefined (which is the default return type of any function without an actual return defined -- which is what your getMyPhotos does)
Instead, you want to do something like this:
function getMyPhotos(callback) {
FB.api('/me/albums', function (response) {
// process respose data to get a list of pictures, as you have already
// shown in your example
// instead of 'returning' pictures,
// we just call the method that should handle the result
callback(pictures);
});
}
// This is the function that actually does the work with your pictures
function oncePhotosReceived(pictures){
for (var i = 0; i < pictures.length; i++) {
// do something with each picture
}
};
// Request the picture data, and give it oncePhotosReceived as a callback.
// This basically lets you say 'hey, once I get my data back, call this function'
getMyPhotos(oncePhotosReceived);
I highly recommend you scrounge around SO for more questions/answers about AJAX callbacks and asynchronous JavaScript programming.
EDIT:
If you want to keep the result of the FB api call handy for other code to use, you can set the return value onto a 'global' variable in the window:
function getMyPhotos(callback) {
FB.api('/me/albums', function (response) {
// process respose data to get a list of pictures, as you have already
// shown in your example
// instead of 'returning' pictures,
// we just call the method that should handle the result
window.pictures = pictures;
});
}
You can now use the global variable 'pictures' (or, explicitly using window.pictures) anywhere you want. The catch, of course, being that you have to call getMyPhotos first, and wait for the response to complete before they are available. No need for localStorage.
As mentioned in the comments, asynchronous code is like Hotel California - you can check any time you like but you can never leave.
Have you noticed how the FB.api does not return a value
//This is NOT how it works:
var result = FB.api('me/albums')
but instead receives a continuation function and passes its results on to it?
FB.api('me/albums', function(result){
Turns out you need to have a similar arrangement for your getMyPhotos function:
function getMyPhotos(onPhotos){
//fetches the photos and calls onPhotos with the
// result when done
FB.api('my/pictures', function(response){
var pictures = //yada yada
onPhotos(pictures);
});
}
Of course, the continuation-passing style is contagious so you now need to call
getMyPhotos(function(pictures){
instead of
var pictures = getMyPhotos();

Categories