I'm trying to count the number of elements returned from $resouce.query() object, so that I can increment it and use it as the ID for the next object to be saved.
Following is a service to communicate with the server:
eventsApp.factory('EventData', function($resource) {
var resource = $resource('/data/event/:id', {id: '#id'});
return {
getEvent: function(eventId) {
return resource.get({id: eventId});
},
saveEvent: function(event) {
var count = 0;
resource.query(function(data) {
count = data.length; // Accessible here!
});
event.id = count; // Not accessible here!
return resource.save(event);
},
getAllEvents: function() {
var count = 0;
var lol = resource.query(function(data) {
count = data.length;
});
console.log(count);
return resource.query();
}
}
});
However, as mentioned in the comments, I'm unable to access the length property. Any solutions?
By looking at your code what I am getting is your resource.query executes the callback function asynchronously because of that your event.id = count; is executed first and then the callback it executed. If you want to access data.length then you can use $q and create a defer. And then resolve that deferred object.
Related
I have an angular view that has a table of rows consisting of a select list and an text box. When a select list index is changed, I need to update the corresponding text box on the same row with a lookup value from the database. I am using ng-Change on the select list to call a $scope function that utilizes $http.get to make the call through an ActionMethod. I have tried this in a million ways, and finally was able to extract a value from the $http.get function by assigning it to a scope variable, but I only ever get the value of the previous lookup triggered by the selected index change, not the current one. How can I get a value real-time? I understand it is asynchronous, so I know the nature of the problem. How do I work around it? Current state of my .js:
$scope.EntityId = null;
$scope.EntityNameChanged = function (item, block) {
for (var i = 0; i < block.length; i++)
{
if (item.Value == block[i].Name.Value) {
$scope.GetEntityId(item.Value);
block[i].Id = $scope.EntityId;
}
}
}
$scope.GetEntityId = function(name) {
$http.get("EntityId", { params: { EntityName: name } }).then(function success(response) {
$scope.EntityId = response.data[0].Value;
});
};
The GetEntityID function should return a promise
function GetEntityId(name) {
//save httpPromise
var p = $http.get("EntityId", { params: { EntityName: name } });
//return derived promise
return p.then(function onSuccess(response) {
//return chained data
return response.data[0].Value;
});
};
Then use an IIFE in the for loop.
$scope.EntityNameChanged = function (item, block) {
for (var i = 0; i < block.length; i++) {
//USE IIFE to hold value of i
(function IIFE(i) {
if (item.Value == block[i].Name.Value) {
//save promise
var p = GetEntityId(item.Value);
//extract value from promise
p.then(function onSuccess(Value) {
block[i].Id = Value;
});
}
})(i);
}
}
Because the onSuccess function gets invoked asynchronously after the for loop completes, an IIFE closure is necessary to preserve the value of i until after the data is returned from the server.
Your GetEntityId function is not async, even though it makes an async request. by the time it sets $scope.EntityId, the for loop has already exited.
You can't actually queue up async calls like this, because each one of them is trying to share a value outside the loop that could be set by any other iteration, so one item in the loop might get another item's return value.
Instead, you should return the promise back to the loop, and perform your .then in the loop. Something like the following:
$scope.EntityNameChanged = function(item, block) {
for (var i = 0; i < block.length; i++) {
if (item.Value == block[i].Name.Value) {
$scope.GetEntityId(item.Value)
.then(function success(response) {
block[i].Id = response.data[0].Value;
});
}
}
}
$scope.GetEntityId = function(name) {
return $http.get("EntityId", {
params: {
EntityName: name
}
});
};
(note this is untested, but should do what you expect).
To prompt Angular to update the value of the scope on its $watch loop, call $scope.apply() after assignment. This should bring you to the most recent value.
EDIT: This answer was wrong. It was a $http get request, which already uses $apply.
What you need to do is put the request inside a factory and return a promise. Require the factory in your controller.
app.factory('getData', function ($http, $q){
this.getlist = function(){
return $http.get('mylink', options)
.then(function(response) {
return response.data.itemsToReturn;
});
}
return this;
});
app.controller('myCtrl', function ($scope, getData){
app.getData()
.then(function(bar){
$scope.foo = bar;
});
});
$http.get("./data/web.json") request succesfully and return a array. When I loop the array by doing request, the iterator variable i would be undefined?! So how can I access the return array and doing the loop request?
<script>
var ngApp = angular.module("webApp", ['xml'])
.config(function ($httpProvider) {
$httpProvider.interceptors.push('xmlHttpInterceptor');
})
.controller("webCtrl", function($scope, $http) {
$http.get("./data/web.json")
.success(function(response) {
$scope.websites = response;
for (i = 0; i < $scope.websites.length; i++){
$http.get('../api/alexa?url=' + $scope.websites[i].url)
.success(function(rsp) {
//$scope.websites[i].rank = rsp.data.POPULARITY.TEXT;
console.log($scope.websites[i]);
});
}
console.log(response);
});
});
</script>
.controller("webCtrl", function ($scope, $http) {
$http.get("./data/web.json")
.success(function (response) {
$scope.websites = response;
for (i = 0; i < $scope.websites.length; i++) {
$scope.processWebsites($scope.websites[i], i);
}
});
$scope.processWebsites = function (website, index) {
$http.get('../api/alexa?url=' + website.url)
.success(function (rsp) {
console.log($scope.websites[index]);
});
}
});
Try this code. This will create a new execution context, thereby removing any unintentional side effects due to async execution.
You want to access i variable but if your request taking to much time then for
loop will not wait it will do ajax call and execute so after for loop
end your i will be $scope.websites.length + 1 (because of i++) so you will get undefined to solve this problem you have to use closure function
JavaScript closure inside loops – simple practical example
var funcs = [];
function createfunc(i) {
return function() {
$http.get('../api/alexa?url=' + $scope.websites[i].url)
.success(function(rsp) {
//$scope.websites[i].rank = rsp.data.POPULARITY.TEXT;
console.log($scope.websites[i]);
});
};
}
$http.get("./data/web.json")
.success(function(response) {
$scope.websites = response;
for (i = 0; i < $scope.websites.length; i++) {
funcs[i] = createfunc(i)
$http.get('../api/alexa?url=' + $scope.websites[i].url)
.success(function(rsp) {
//$scope.websites[i].rank = rsp.data.POPULARITY.TEXT;
});
}
console.log(response);
});
for (i = 0; i < funcs.length; i++) {
funcs[i]();
}
I am not sure How your response json looks like but it should be array of key value pairs or a single key value pair
so if you have like
[ {key:value},{key:value},{key:value}]
as response assuming a key is url in your case
it should directly work for you now you are asking for a key called i
that is website[i] which is undefined.
try doing this
foreach loop
i isn't defined at your second success callback since it is an async callback and when it is been called the parent scope isn't valid anymore since it was executed without defining any local variables, you should define the iterator inside the for loop so it will be proper declared and hoisted
but you should be aware that since it is an async callback you will have a race condition when most of the chances that the loop will end before the first call back will be called and the iterator value in all of the iteration will be the array size (the last value)
var a = [1, 2, 4]
for (var i = 0; i < a.length; i++) {
var that = this;
that.iterator = i;
setTimeout(function() {
alert(a[that.iterator]);
}, 10);
}
I would suggest you to aggregate the calls and use an aggregated callback to handle all of them together using $q.all
I'm trying to build a simple Twitter feed app, but I'm having difficulty implementing a refresh function.
$scope.refreshTimeline = function() {
for (x = 0; x < $scope.tweetsarray.length; x++){ // loop through the twitter feeds
twitterService.getLatestTweets($scope.tweetsarray[x].name).then(function(data) {
$scope.tweetsarray[x].tweets = data; //update each
});
}
}
and the getlatesttweets function
getLatestTweets: function (name) {
//create a deferred object using Angular's $q service
var deferred = $q.defer();
var promise = authorizationResult.get('https://api.twitter.com/1.1/search/tweets.json?q='+name+'&count=8').done(function(data) {
//when the data is retrieved resolved the deferred object
deferred.resolve(data)
});
//return the promise of the deferred object
return deferred.promise;
}
So here are the issues I'm having
1) The value of x (global variable, unused anywhere else) in the .then(function(data) seems to be different from anywhere else in the loop and it doesn't seem to increment. I've tried using $scope.x or making it a function variable in the loop. The refresh works if I hardcode it to only refresh [0] in the tweetsarray, but[x] returns a separate value.
2) I'm guessing there is some kind of issue with trying to loop through a function with deferred promises. Is there a way I can make sure on each pass through the loop that the promise is returned before continuing?
Thanks I appreciate your help!
-Dave
Due to the closure behavior of javascript, it will take the latest x value at the time of callback from service. So try in this way.
$scope.refreshTimeline = function() {
for (var x = 0; x < $scope.tweetsarray.length; x++){
storeTweets(x);
}
}
function storeTweets(x){
twitterService.getLatestTweets($scope.tweetsarray[x].name).then(function(data) {
$scope.tweetsarray[x].tweets = data; //update each
});
}
You could do something like this
$scope.refreshTimeline = function() {
for (x = 0; x < $scope.tweetsarray.length; x++){ // loop through the twitter feeds
var data;
data.tweet = $scope.tweetsarray[x];
twitterService.getLatestTweets($scope.tweetsarray[x].name).then(function(data) {
$scope.tweetsarray[$scope.tweetsarray.indexof(data.tweet)].tweets = data; //update each
});
}
}
it means, pass the array element to the async task and it comes back in the response and then use it to find the array index and update the element
How do you go about knowing when a For Loop is done iterating and attach a callback.
This is a sample loop of many loops within a function that inserts records into indexedDB.
if (Object.hasOwnProperty("Books")) {
for (var i = 0, j = Object["Books"].length; i < j; i++) {
server.Book.add({
title: Object["Books"][i].Cat,
content: Object["Books"][i]
});
}
}
I need to be able to know when each of the if statements loops are finished then attach a callback. All the loops are being fired asynchronously, and I need to run a function_final() just when all loops are finished not when they are fired.
EDIT
What I have tried so far :
InsertDB = {
addBook: function(Object) {
return $.Deferred(function() {
var self = this;
setTimeout(function() {
if (Object.hasOwnProperty("Book")) {
for (var i = 0, j = Object["Book"].length; i < j; i++) {
server.Book.add({
title: Object["Book"][i].id,
content: Object["Book"][i]
});
}
self.resolve();
}
}, 200);
});
},
addMagaz: function(Object) {
return $.Deferred(function() {
var self = this;
setTimeout(function() {
if (Object.hasOwnProperty("Magaz")) {
for (var i = 0, j = Object["Magaz"].length; i < j; i++) {
server.Magaz.add({
content: Object["Magaz"][i]
});
}
self.resolve();
}
}, 2000);
});
},
addHgh: function(Object) {
return $.Deferred(function() {
var self = this;
setTimeout(function() {
if (Object.hasOwnProperty("MYTVhighlights")) {
for (var i = 0, j = Object["MYTVhighlights"].length; i < j; i++) {
server.MYTVhighlights.add({
content: Object["MYTVhighlights"][i]
});
}
self.resolve();
}
}, 200);
});
}, ect...
then on a AJAX success callback :
success: function(data){
var Object = $.parseJSON(data);
$.when(InsertDB.addBook(Object),
InsertDB.addMagaz(Object),
InsertDB.addUser(Object),
InsertDB.addArticles(Object),
InsertDB.addHgh(Object),
InsertDB.addSomeC(Object),
InsertDB.addOtherC(Object)).done(final_func);
function final_func() {
window.location = 'page.html';
}
Here final_func is fired before looping ends..
Thanks
You can use JavaScript closures, just like this:
if (Object.hasOwnProperty("Books")) {
for (var i = 0, j = Object["Books"].length; i < j; i++) {
(function(currentBook)
server.Book.add({
title: currentBook.Cat,
content: currentBook
});
)(Object["Books"][i]);
}
function_final();
}
For more information about closures you can refer here.
Since you've said that server.Book.add() is asynchronous, you will need a method of knowing when that asynchronous operation is completed and you can then use that to build a system for knowing when all of them are done. So, the pivotal question (which I already asked as a comment earlier and you have not responded to) is how you can know when server.Book.add() is actually complete. If you're using an indexedDB, then somewhere inside that function, there is probably a request object that has an onsuccess and onerror methods that will tell you when that specific operation is done and that information needs to get surfaced out to server.Book.add() in some way, either as a completion callback or as a returned promise (those two options are how $.ajax() operates for it's asynchronous behavior.
Let's suppose that server.Book.add() returns a promise object that is resolved or rejected when the asychronous .add() operation is complete. If that was the case, then you could monitor the completion of all the operations in your loop like this:
if (obj.hasOwnProperty("Books")) {
var promises = [], p;
for (var i = 0, j = obj["Books"].length; i < j; i++) {
p = server.Book.add({
title: obj["Books"][i].Cat,
content: obj["Books"][i]
});
promises.push(p);
}
$.when.apply($, promises).done(function() {
// this is executed when all the promises returned by
// server.Book.add() have been resolved (e.g. completed)
}).error(function() {
// this is executed if any of the server.Book.add() calls
// had an error
});
}
Let's suppose that instead of server.Book.add() returning a promise, it has a couple callbacks for success and error conditions. Then, we could write the code like this:
if (obj.hasOwnProperty("Books")) {
var promises = [], p;
for (var i = 0, j = obj["Books"].length; i < j; i++) {
(function() {
var d = $.Deferred();
server.Book.add({
title: obj["Books"][i].Cat,
content: obj["Books"][i],
success: function() {
var args = Array.prototype.slice.call(arguments, 0);
d.resolve.apply(d, args);
},
error: function() {
var args = Array.prototype.slice.call(arguments, 0);
d.reject.apply(d, args);
}
});
promises.push(d.promise());
})();
}
$.when.apply($, promises).done(function() {
// this is executed when all the promises returned by
// server.Book.add() have been resolved (e.g. completed)
}).error(function() {
// this is executed if any of the server.Book.add() calls
// had an error
});
}
So, since you've not disclosed how server.Book.add() actually indicates its own completion, I can't say that either of these blocks of code work as is. This is meant to demonstrate how you solve this problem once you know how server.Book.add() communicates when it is complete.
Promises/Deferreds are not magic in any way. They don't know when an operation is completed unless that operation calls .resolve() or .reject() on a promise. So, in order to use promises, your async operations have to participate in using promises or you have to shim in a promise to an ordinary completion callback (as I've done in the second code block).
FYI, I also change your Object variable to obj because defining a variable named Object that conflicts with the built-in Object in the javascript language is a bad practice.
use jquery when functionality
$.when( function1 , function2 )
.then( myFunc, myFailure );
I'd write something like this in pure JS, consider it as pseudo code:
var totalForLoopsCount = 3; //Predict for loops count here
var forLoopsFinished = 0;
function finalFunction()
{
alert('All done!');
}
function forLoopFinished()
{
forLoopsFinished++;
if(forLoopsFinished == totalForLoopsCount)
{
finalFunction();
}
}
var length = 10; //The length of your array which you're iterating trough
for(var i=0;i<length;i++)
{
//Do anything
if(i == length-1)
{
forLoopFinished();
}
}
for(var i=0;i<length;i++)
{
//Do anything
if(i == length-1)
{
forLoopFinished();
}
}
for(var i=0;i<length;i++)
{
//Do anything
if(i == length-1)
{
forLoopFinished();
}
}
I am having problem with calling the generated functions in serial. I am using the async library and the code seems to work when there is no deep callback calling needed. When I add the real scenario it throws errors.
Here is the example which works, returns the array of 0 to 4:
Scrape.prototype.generatePageFunctions = function() {
var functionList = new Array(), self = this;
for (var i = 0; i < this.pageSet; i++) {
(function(i) {
functionList.push(function(cb) {
// Inner functions which will be called in seriers
var timeoutTime = parseInt(Math.random() * 5000 + 3000, 10);
setTimeout(function() {
self.setIndex(i);
//self.getSite(function)
cb(null, i);
}, timeoutTime);
});
})(i);
}
return functionList;
}
Scrape.prototype.run = function() {
var functionList = this.generatePageFunctions();
async.series(functionList, function(err, results) {
console.log('Job is completed ');
console.log(results);
});
}
Now adding the real scenario like downloading the site and then put in callback:
Scrape.prototype.generatePageFunctions = function() {
var functionList = new Array(), self = this;
for (var i = 0; i < this.pageSet; i++) {
(function(i) {
functionList.push(function(cb) {
// Inner functions which will be called in seriers
var timeoutTime = parseInt(Math.random() * 5000 + 3000, 10);
setTimeout(function() {
self.setIndex(i);
self.getSite(function(result) {
// Async callback to pass the data
cb(null, result);
});
}, timeoutTime);
});
})(i);
}
return functionList;
}
Error is like this, even if passing instead of result iterator variable i:
/home/risto/scrape/node_modules/async/lib/async.js:185
iterator(x.value, function (err, v) {
^
TypeError: Cannot read property 'value' of undefined
at /home/risto/scrape/node_modules/async/lib/async.js:185:23
at /home/risto/scrape/node_modules/async/lib/async.js:108:13
at /home/risto/scrape/node_modules/async/lib/async.js:119:25
at /home/risto/scrape/node_modules/async/lib/async.js:187:17
at /home/risto/scrape/node_modules/async/lib/async.js:491:34
at /home/risto/scrape/scraper/scrape.js:114:13
at /home/risto/scrape/scraper/scrape.js:64:16
at Object.<anonymous> (/home/risto/scrape/scraper/engines/google.js:58:12)
at Function.each (/home/risto/scrape/node_modules/cheerio/lib/api/utils.js:133:19)
at [object Object].each (/home/risto/scrape/node_modules/cheerio/lib/api/traversing.js:69:12)
// Edit
Only result which gets added into the complete callback is the first one, other functions are never called.
Also for information the functions return object literals if that does matter.
There is nothing wrong with your code. Creating a simple testcase shows that.
I created a mock:
Scrape = function() {
this.pageSet = 5;
}
Scrape.prototype.setIndex = function() {
}
Scrape.prototype.getSite = function(cb) {
cb('works');
}
and calling the run method it outputs the expected:
[ 'works', 'works', 'works', 'works', 'works' ]
So the problem is somewhere else. Have you tried to check the functionList variable in your run method?
Thank you #KARASZI István, all the code above is correct, the problem seemed in somewhere else. The deepest callback got called multiple times but the outer one got called only once.