I have a two calls,for match and matches. Match gets the data for one specific match, and matches the whole batch, with some relative ratings. I need to pass those relative ratings to a matching specific match.
Service that returns the json data from different API endpoints
function matchService($http, $q, API_URL) {
function getMatchesForVacancy(vacancyId) {
var deferred = $q.defer();
$http.get(API_URL + vacancyId + '/matches') //v2.0 lib
.then(function matchesArrived(matchData) {
deferred.resolve(matchData.data);
}, function matchesFailed(error) {
deferred.reject(error);
});
return deferred.promise;
}
function getMatchForVacancy(vacancyId, accountId) {
var deferred = $q.defer();
$http.get(API_URL + vacancyId + '/matches/' + accountId)
.then(function matchesArrived(matchData) {
//matchData.data.candidateRating = {overallScore: 0.65}; - ie. what I need to get
deferred.resolve(matchData.data);
}, function matchesFailed(error) {
deferred.reject(error);
});
return deferred.promise;
}
Matches and match are displayed in the different views, so I have separate controller files. Now in the single match controller I am trying to pass in the specific rating where id's are the same.
So what I tried to do, was in candidate controller create a function like this:
function connectRating(vacancyId, match){
var matches = matchService.getMatchesForVacancy(vacancyId);
for(var i = 0; i < matches.length; i++){
if(matches[i].accountId === match.accountId){
match.candidateRating.overralScore = matches[i].candidateRating.overralScore;
return match;
}
}
}
The json for matches is like this:
[
{
"accountId": 0,
"candidateRating": {
"overallScore": 0
}
}
]
And somehow the right data is not passed, or anything for that matter. I must be missing something, so I would appreciate any help.
You are missing the successful function of your promise. That's when you have a guarantee of the asynchronous result. Try this:
matchService.getMatchesForVacancy(vacancyId).then(function(d){
// the return data is in "d"
// do whatever you want with it
});
Related
I want to populate some values in Json that are being calculated with angular-promises and these value should be updated after certain events.
I tried to call the factory which yields the values for example something like below and tried to call the functions GetWeeklyVal and GetDailyVal which are in charge of calculating the values :
this.salesList =
{"sales":[
{ "id":"A1", "dailyValue": GetDailyVal('A1'), "weeklyValue": GetWeeklyVal('A1')},
{ "id":"A2", "dailyValue": GetDailyVal('A2'), "weeklyValue": GetWeeklyVal('A2')}
]}
and in my controller I have:
$scope.sales= salesServices.salesList.sales;
but it didn't work. the values remain zero which is the default value in the application.
Why the values are not being updated and what would be a better solution?
update
This is the portion of the code I call the calculation functions: (I skip the portion to get the values based on passed id in here)
function GetDailyVal(id){
var dValue = 0;
salesService.getSales();
dValue = salesService.totalAmount;
return dValue;
}
this is the factory
.factory('salesService', ['$http', '$q'],
function salesInvoiceService($http, $q) {
var service = {
sales: [],
getSales: getSales,
totalAmount: 0
};
return service;
function getSales() {
var def = $q.defer();
var url = "http://fooAPI/salesinvoice/SalesInvoices"; //+ OrderDate filter
$http.get(url)
.success(function(data) {
service.sales = data.d.results;
setTotalAmount(service.sales);
def.resolve(service.sales);
})
.error(function(error){
def.reject("Failed to get sales");
})
.finally(function() {
return def.promise;
});
}
function setTotalAmount(sales){
var sum = 0;
sales.forEach(function (invoice){
sum += invoice.AmountDC;
});
service.totalAmount = sum;
}
})
I think there are some errors in your code.
I give some sample code here. I think this will help you.
This is a sample code in one of my application. Check it.
service.factory('Settings', ['$http','$q', function($http,$q) {
return {
AcademicYearDetails : function(Details) {
return $http.post('/api/academic-year-setting', Details)
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
return $q.reject(response.data);
}
}, function(response) {
return $q.reject(response.data);
});
},
newUser : function(details) {
return $http.post('/api/new-user', details);
}
}
}]);
The reason why its not working is:
dailyValue: GetDailyVal('A1')
Here, GetDailyVal makes an async ajax call to an api. For handling async requests, you have to return a promise as follows in your GetDailyVal function as follows:
function GetDailyVal() {
salesService.getSales().then(function(data) { //promise
dValue = salesService.totalAmount;
return dValue;
})
}
Same thing need to be done for weeklyValue.
I have the following function which creates an array of objects inside models, however when I come to use models further down the app I'm unable to loop through it's contents to pull out data.
Every loop method I've tried so far containing a single console.log() message just prints out the message once message when models will contain two objects so I think the problem actually lies with the creation of models. If I create a promise and print out the value of models.devices when it's finished processing an empty array is returned.
Any ideas?
var d = devices.split(','),
count = 0,
models = {devices:[]};
angular.forEach(d, function (device, i) {
var index = i;
if (index <= 1) {
var deviceName = device.replace(/ /g,"+").toLowerCase(),
req = '?__url_path_param=' + deviceName;
$http
.get('/api/cheapest_by_name' + req)
.success(function (obj) {
models.devices.push(obj.device);
count++;
});
}
});
$q.all(models).then(function (data) {
apiDeal.multi(data, 3, 2);
});
Then... (in api-deal.factory.js)
function apiDeal($http, $rootScope) {
return {
multi: function (devices, limit, type) {
console.log(devices); // equal to below image
console.log(devices.devices); // equal to '[]'
}
}
}
I then need to loop through devices in apiDeal.multi
You need to keep an array of promises, which should replace the models you're using the $q.all on. It has to be an array of promises.
So change your code to this:
var d = devices.split(','),
count = 0,
models = {devices:[]},
promises = [];
var promise = $http
.get('/api/cheapest_by_name' + req)
.success(function (obj) {
models.devices.push(obj.device);
count++;
});
promises.push(promise);
And then, do:
$q.all(promises).then(function (data) {
apiDeal.multi(data, 3, 2);
});
Simple Fiddle demonstration
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
I'm trying to build a service that can take CSV data, convert it to rows, and then make an API request for/from each row, adding the formatted results to an output string.
In other words, my (Coffeescript) code looks like this:
$s.batchFetch = ->
return unless $s.csv
$s.output = ''
for row in $s.csv.split("\n")
$s._addToOutput(row)
The $s._addToOutput() function correctly makes an API call using the row, formats it, and adds the formatted response to my output string ($s.output). Basically, something like this:
$s._addToOutput (row) = ->
formattedResponse = ''
$http.get("api/request/path/row-specific-whatever")
.success (res) ->
formattedResponse = $s._format(res)
.then ->
$s.output += formattedResponse
The problem is that the order of the formatted responses in my output string seems random/variable. It looks like the API takes faster/longer for certain rows than others, and whichever response comes back first gets added first -- with no respect for the order of my rows variable.
I figure the solution is some sort of Angular promise chaining, a la:
$s._addToOutput(row).then ->
$s._addToOutput(secondRow).then ->
$s._addToOutput(thirdRow).then ->
...
But I have an unpredictable number of rows coming in, and I'd love to be able to essentially just say: "Make the API calls for each row, one after the other."
Can anyone think of a good way to do this? I may just not be thinking straight right now, but I'm stumped.
Thanks!
Sasha
EDIT -- Gave ryeballar's solution a shot, but my implemenation of it is not actually stopping the reordering problem. Pretty sure it's a mistake on my end, so if anyone spots anything, please let me know:
(Note, I had to adapt the solution, because I make two consecutive requests for each row -- the first for the "venue" and the second for the "photo" of the venue I find. Also, yamlify == 'format'.)
$s.batchFetch = function() {
if (!$s.csv) {
return;
}
$s.output = '';
return $scope.csv.split("\n").reduce(function(promise, row) {
var rowPromise, split;
split = row.split(',');
rowPromise = $s._getVenue(split[0], split[1]).success(function(res) {
var venue;
venue = res.response.groups[0].items[0].venue;
$s._getPhoto(venue).success(function(resp) {
var photo;
photo = $s._photoUrl(resp);
return $s.output += $s._yamlify(venue, row, photo);
});
});
return promise.then(rowPromise);
}, $q.when());
};
Note -- getVenue() and getPhoto() are just calls to $http, so they return objects that respond to success, error, then, etc. photoUrl() is just a helper function for parsing the response object into a new API path.
Latest effort, which is still reordering randomly -- yes _getVenue and _getPhoto are just $http.get(path) calls:
$s.batchFetch = function() {
if (!$s.csv) {
return;
}
$s.output = '';
return $s.csv.split("\n").reduce(function(promise, row) {
var rowPromise, split;
split = row.split(',');
rowPromise = $s._getVenue(split[0], split[1]).success(function(res) {
var venue;
venue = res.response.groups[0].items[0].venue;
return $s._getPhoto(venue).success(function(resp) {
var photo;
photo = $s._photoUrl(resp);
return $s.output += $s._yamlify(venue, row, photo);
});
});
return promise.then(rowPromise);
}, $q.when());
};
You can use .reduce() by invoking each promise from one to the next. The initial value is a promise that will resolve immediately $q.when() and then invoke rowPromise which is also a promise, which creates a chaining effect whenever each then()s is invoked.
.controller('CsvController', function($scope, $q) {
$scope.csv = '.....';
$scope._format = function() {/*...*/};
$scope.batchFetch = function() {
$scope.output = '';
return $scope.csv.split('\n').reduce(function(promise, row) {
return promise.then(function() {
return $http.get("api/request/path/row-specific-whatever", {row: row})
.success(function(res) {
$scope.output += $scope._format(res);
});
});
}, $q.when());
};
});
UPDATE:
I have updated the code above, it should have been a callback instead of invoking the $http request during the iteration process. So your code should be something like this:
$s.batchFetch = function() {
if (!$s.csv) {
return;
}
$s.output = '';
return $s.csv.split("\n").reduce(function(promise, row) {
return promise.then(function() {
var split = row.split(',');
return $s._getVenue(split[0], split[1]).success(function(res) {
var venue = res.response.groups[0].items[0].venue;
return $s._getPhoto(venue).success(function(resp) {
var photo = $s._photoUrl(resp);
return $s.output += $s._yamlify(venue, row, photo);
});
});
});
}, $q.when());
};
As I read through your code, it seems to be falling on to a callback hell. Alternatively, it would be better to structure it like this instead:
$s.batchFetch = function() {
if (!$s.csv) {
return;
}
$s.output = '';
return $s.csv.split("\n").reduce(function(promise, row) {
var split, venue, photo;
return promise
.then(function() {
split = row.split(',');
return $s._getVenue(split[0], split[1]);
}).then(function(response) {
venue = response.data.response.groups[0].items[0].venue;
return $s._getPhoto(venue);
}).then(function(response) {
photo = $s._photoUrl(response.data);
return $s.output += $s._yamlify(venue, row, photo);
});
}, $q.when());
};
I would like to write a javascript function that returns informations from youtube videos; to be more specific I would like to get the ID and the length of videos got by a search, in a json object. So I took a look at the youtube API and I came out with this solution:
function getYoutubeDurationMap( query ){
var youtubeSearchReq = "https://gdata.youtube.com/feeds/api/videos?q="+ query +
"&max-results=20&duration=long&category=film&alt=json&v=2";
var youtubeMap = [];
$.getJSON(youtubeSearchReq, function(youtubeResult){
var youtubeVideoDetailReq = "https://gdata.youtube.com/feeds/api/videos/";
for(var i =0;i<youtubeResult.feed.entry.length;i++){
var youtubeVideoId = youtubeResult.feed.entry[i].id.$t.substring(27);
$.getJSON(youtubeVideoDetailReq + youtubeVideoId + "?alt=json&v=2",function(videoDetails){
youtubeMap.push({id: videoDetails.entry.id.$t.substring(27),runtime: videoDetails.entry.media$group.media$content[0].duration});
});
}
});
return youtubeMap;
}
The logic is ok, but as many of you have already understood because of ajax when I call this function I get an empty array. Is there anyway to get the complete object? Should I use a Deferred object? Thanks for your answers.
Yes, you should use deferred objects.
The simplest approach here is to create an array into which you can store the jqXHR result of your inner $.getJSON() calls.
var def = [];
for (var i = 0; ...) {
def[i] = $.getJSON(...).done(function(videoDetails) {
... // extract and store in youtubeMap
});
}
and then at the end of the whole function, use $.when to create a new promise that will be resolved only when all of the inner calls have finished:
return $.when.apply($, def).then(function() {
return youtubeMap;
});
and then use .done to handle the result from your function:
getYoutubeDurationMap(query).done(function(map) {
// map contains your results
});
See http://jsfiddle.net/alnitak/8XQ4H/ for a demonstration using this YouTube API of how deferred objects allow you to completely separate the AJAX calls from the subsequent data processing for your "duration search".
The code is a little long, but reproduced here too. However whilst the code is longer than you might expect note that the generic functions herein are now reusable for any calls you might want to make to the YouTube API.
// generic search - some of the fields could be parameterised
function youtubeSearch(query) {
var url = 'https://gdata.youtube.com/feeds/api/videos';
return $.getJSON(url, {
q: query,
'max-results': 20,
duration: 'long', category: 'film', // parameters?
alt: 'json', v: 2
});
}
// get details for one YouTube vid
function youtubeDetails(id) {
var url = 'https://gdata.youtube.com/feeds/api/videos/' + id;
return $.getJSON(url, {
alt: 'json', v: 2
});
}
// get the details for *all* the vids returned by a search
function youtubeResultDetails(result) {
var details = [];
var def = result.feed.entry.map(function(entry, i) {
var id = entry.id.$t.substring(27);
return youtubeDetails(id).done(function(data) {
details[i] = data;
});
});
return $.when.apply($, def).then(function() {
return details;
});
}
// use deferred composition to do a search and then get all details
function youtubeSearchDetails(query) {
return youtubeSearch(query).then(youtubeResultDetails);
}
// this code (and _only_ this code) specific to your requirement to
// return an array of {id, duration}
function youtubeDetailsToDurationMap(details) {
return details.map(function(detail) {
return {
id: detail.entry.id.$t.substring(27),
duration: detail.entry.media$group.media$content[0].duration
}
});
}
// and calling it all together
youtubeSearchDetails("after earth").then(youtubeDetailsToDurationMap).done(function(map) {
// use map[i].id and .duration
});
As you have discovered, you can't return youtubeMap directly as it's not yet populated at the point of return. But you can return a Promise of a fully populated youtubeMap, which can be acted on with eg .done(), .fail() or .then().
function getYoutubeDurationMap(query) {
var youtubeSearchReq = "https://gdata.youtube.com/feeds/api/videos?q=" + query + "&max-results=20&duration=long&category=film&alt=json&v=2";
var youtubeVideoDetailReq = "https://gdata.youtube.com/feeds/api/videos/";
var youtubeMap = [];
var dfrd = $.Deferred();
var p = $.getJSON(youtubeSearchReq).done(function(youtubeResult) {
$.each(youtubeResult.feed.entry, function(i, entry) {
var youtubeVideoId = entry.id.$t.substring(27);
//Build a .then() chain to perform sequential queries
p = p.then(function() {
return $.getJSON(youtubeVideoDetailReq + youtubeVideoId + "?alt=json&v=2").done(function(videoDetails) {
youtubeMap.push({
id: videoDetails.entry.id.$t.substring(27),
runtime: videoDetails.entry.media$group.media$content[0].duration
});
});
});
});
//Add a terminal .then() to resolve dfrd when all video queries are complete.
p.then(function() {
dfrd.resolve(query, youtubeMap);
});
});
return dfrd.promise();
}
And the call to getYoutubeDurationMap() would be of the following form :
getYoutubeDurationMap("....").done(function(query, map) {
alert("Query: " + query + "\nYouTube videos found: " + map.length);
});
Notes:
In practice, you would probably loop through map and display the .id and .runtime data.
Sequential queries is preferable to parallel queries as sequential is kinder to both client and server, and more likely to succeed.
Another valid approach would be to return an array of separate promises (one per video) and to respond to completion with $.when.apply(..), however the required data would be more awkward to extract.