I'm using $q in a method to get an array of objects. I also have a method called getItems which makes use of this promise (all).
I use all a second time at the bottom, to do stuff with $scope.list. The code has to be wrapped inside all().then(function() { ... } so it only triggers when $scope.list ready.
var all = function() { return $q.all([service.getAllItems()]) }
var getItems = function() {
all().then(function(value) {
$scope.list = JSON.parse(value)
}, function(reason) {
$scope.result = reason
})
}
getItems()
all().then(function() {
// do stuff with $scope.list
}
This works...almost. Sometimes the first all finishes first and sometimes the second one. So sometimes $scope.list has the objects and sometimes it's empty.
How to create a new promise that only triggers when all fetches the array of objects?
You can do it like so:
var all = function() { return $q.all([service.getAllItems()]) }
var getItems = function() {
return all().then(function(value) {
$scope.list = JSON.parse(value)
}, function(reason) {
$scope.result = reason
});
}
getItems().then(function() {
// do stuff with $scope.list
}
If you return a promise in a function you can chain a .then to it, so now your getItems will return the promise from all(), once this is fulfilled your code will continue
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;
});
});
i use angular.js in front side.
in my controller.js i defined an init() method that will be called in
init of my controller.
Init method definition:
var init = function () {
$scope.callTeamsService();
if ($scope.teams.length == 0){
....
}else{
...
}
.....
};
in $scope.callTeamsService i filled in $scope.teams variable.
$scope.callTeamsService method definition:
$scope.callTeamsService = function(){
NavService.getTeams(function (response) {
$timeout(function () {
$scope.teams = response;
}
}, 200);
});
};
In my service.js i defined a getTeams method as follow:
service.getEquipes = function (callback) {
$http.get(urlBase+'users/' + $rootScope.globals.currentUser.loggedUser.idUser + '/teams')
.success(function (response) {
callback(response);
});
};
My problem is when $scope.teams.length == 0 condition is reached the
service.getEquipes method in my service.js is not yet called.
How can i modify this code in order to finish the execution of $scope.callTeamsService method before reaching $scope.teams.length == 0 condition.
service/factory
service.getEquipes = function () {
return $http.get(urlBase+'users/' + $rootScope.globals.currentUser.loggedUser.idUser + '/teams');
};
// controller
var promise = NavService.getTeams.then (
function(data) {
//assign to $scope or do logic
},
function(err){
console.log(err)
}
)
How can i modify this code in order to finish the execution of $scope.callTeamsService method before reaching $scope.teams.length == 0 condition.
That's the wrong way round - you need to wait with executing the $scope.teams.length == 0 condition until the $scope.callTeamsService method has finished.
The classical method would be to give the $scope.callTeamsService method a callback parameter and call that in the timeout instead of $scope.teams = response;. Then you can put your condition in the init function in the callback that you pass.
However, you seem to want to use promises. For that, all of your functions (that all are asynchronous) should return a promise:
service.getEquipes = function (callback) {
return $http.get(urlBase+'users/' + $rootScope.globals.currentUser.loggedUser.idUser + '/teams');
}
(that was easy, $http already returns promises)
$scope.callTeamsService = function() {
return NavService.getTeams().then(function(teams) {
return $timeout(function() {
return teams;
}, 200);
});
};
(and $timeout does as well - by invoking then and returning it from the callback you can chain them and get a new promise for both)
function init() {
return $scope.callTeamsService().then(function(teams) {
$scope.teams = teams;
if (teams.length == 0) {
…
} else {
…
}
});
}
I have a factory that looks like such:
app.factory('thingFactory', function($http) {
var factory = {};
var things = [];
factory.refreshThings = function() {
return $http.post('/GetThings');
}
factory.initializeThings = factory.refreshThings()
.then(function(response) {
things = response.data;
}, function(response){
// some error handling code here...
});
factory.getThings = function() {
return things;
}
return factory;
}
and a controller
app.controller('myController', function($scope, thingFactory) {
$scope.things = thingFactory.getThings();
}
Because of the asynchronous nature of promises, and other collections being initialized (in addition to things), should I be concerned with getThings() returning an empty array, and thus, returning before the $http.post() call has resolved?
Is this a better alternative?
app.controller('myController', function($scope, thingFactory) {
$scope.things = []
thingFactory.initializeThings
.then(function(response) {
$scope.things = response.data;
}, function (response) {
// some error handling code here...
});
}
Is there a safe alternative, where I can get the controller to not think about the promise and just safely get the collection from the factory?
You're code is definitely going to be problematic. The factory will not be instantiated until it is used by a controller, so things will not be initialized until it is called by a controller, at which time initializeThings will get called right before you call getThings, which will likely return an empty array. Also, it's never a good idea to follow a "let's hope it's there" approach.
I see two approaches you can take: getThings accepts a callback as an argument or it returns a promise, which could look something like this:
Callbacks - I prefer callbacks over promises, but that's a personal thing. Also, I use NodeJS-inspired syntax:
var things; // no need to initialize array
// callback -> function (error, things) {}
factory.getThings = function (callback) {
if (things) {
return callback(null, things);
}
factory.refreshThings()
.then( function (response) {
things = response.data;
return callback(null, things);
}, function (reason) {
return callback(reason);
});
}
Promises - not the best syntax, but you get the idea
factory.getThings = function () {
var d = $q.defer();
// I'm sure there's a better way to do this, but setting
// this in a timeout will allow the promise to return so that
// you can try to return it.
$timeout( function () {
if (things) {
return d.resolve(things);
}
factory.refreshThings()
.then( function (response) {
things = response.data;
return d.resolve(things);
}, function (reason) {
return d.reject(reason);
});
});
return d.promise;
}
As a side note, if you're using a RESTful API, GET should be used to get information rather than POST.
I've tried to find the answer to this and have started reading about promises / deferred, but when kicked off from somewhere else I don't know how to approach this.
angular.module('myapp.utilities').factory('codetabelService', ['Restangular', function(Restangular) {
var initialized = false;
var listtopopulate1 = [];
var listtopopulate2 = [];
var update = function() {
Restangular.all('codeTabel').getList()
.then(function(codetabellen) {
codetabellen.forEach(function(entry) {
//do some processing on return values
listtopopulate1.push(entry);
listtopopulate2.push(entry);
});
initialized=true;
});
};
return {
initialize: function() {
if (!initialized) {
update();
}
},
getListValuesType1: function() {
//How do I wait here for initialized to become true?
return listtopopulate1;
},
getListValuesType2: function() {
//How do I wait here for initialized to become true?
return listtopopulate2;
}
};
}]);
So what I'm trying to do is cache some values when my single page app starts.
On my root controller I call the initialize method which starts the async call to the backend.
When my views are being loaded and the controller sets the scope values to the result of getListValuesType1() the asynch call is sometimes not yet complete.
Because the async load was not triggered by the controller that called the method getListValuesType1() I'm not sure if promises will work here (I admit, I'm still new to this)
I found out you can put a timer on it, but this didn't seem right. It just feels there's a better solution out there.
Yes you can effectively use promise and promise caching to do this, one way you can achieve this by doing:-
angular.module('myapp.utilities').factory('codetabelService', ['Restangular', '$q', function(Restangular, $q) {
var initialized;//Use this to cache the promise
var listtopopulate1 = [];
var listtopopulate2 = [];
var _initialize = function() {
//If already initialized just return it which is nothing but the promise. This will make sure initialization call is not made
return initialized || (initialized= Restangular.all('codeTabel').getList()
.then(function(codetabellen) {
codetabellen.forEach(function(entry) {
listtopopulate1.push(entry);
listtopopulate2.push(entry);
});
//Note- You could even return the data here
}, function(){
//Just clean up incase call is a failure.
initialized = null;
//Just reject with something if you want to:-
//return $q.reject("SomeError")
}));
};
return {
initialize: function() {
return _initialize(); //Just return promise incase you want to do somthing after initialization
},
getListValuesType1: function() {
return _initialize().then(function(){ //return promise with a chain that resolves to the list
return listtopopulate1;
});
},
getListValuesType2: function() {
return _initialize().then(function(){ //return promise with a chain that resolves to the list
return listtopopulate2;
});
}
};
}]);
And while using it, you could do:-
codetabelService.getListValuesType1().then(function(list){
$scope.list1 = list;
});
With this you can even get rid of the initialize call from the contract and make the ajax call only during the first usage of getListX methods.
promises will work for this. You may need to refactor some things though.
angular.module('myapp.utilities').factory('codetabelService', ['Restangular', function(Restangular) {
var initialized = false;
var listtopopulate1 = [];
var listtopopulate2 = [];
var update = function() {
return Restangular.all('codeTabel').getList()
.then(function(codetabellen) {
codetabellen.forEach(function(entry) {
//do some processing on return values
listtopopulate1.push(entry);
listtopopulate2.push(entry);
});
initialized=true;
});
};
return {
initialize: function() {
if (!initialized) {
this.updatePromise = update();
}
},
getListValuesType1: function() {
//How do I wait here for initialized to become true?
return this.updatePromise.then(function() {
// you'll want to refactor the callee to handle a promise here
return listtopopulate1;
});
},
getListValuesType2: function() {
return this.updatePromise.then(function(){
// you'll want to refactor the callee to handle a promise here
//How do I wait here for initialized to become true?
return listtopopulate2;
});
//How do I wait here for initialized to become true?
}
};
}]);
in my controller i have a function
$scope.orderHistory = {};
$scope.results = {};
var getHistory = function (max,offset)
{
//do some stuff
$scope.orderHistory = data.entities;
angular.forEach($scope.orderHistory,function(value)
{
$scope.results = getResults(value.id);
console.log($scope.results);
});
}
var getResults = function(id)
{
api.getHistoryData(id)
.then ( function (results)
{
return results.data;
});
};
the problem i run into is the getResults function does not seem to actually return the data and have it set to $scope.results inside the getHistory function.
When i do a console.log in (results.data) inside the .then part of getResults function i see the results. Can anyone help me understand why it is not returning the results even though it exists and what i can do to fix it
The function in then(function(){...}) is executed asynchronously, and the containing getResults function won't wait for it to return. More specifically, api.getHistoryData seems to return a promise.
You can use Angular's data binding to work around this:
var getHistory = function (max,offset)
{
//do some stuff
$scope.orderHistory = data.entities;
angular.forEach($scope.orderHistory,function(value)
{
getResults(value.id);
});
}
var getResults = function(id)
{
api.getHistoryData(id)
.then ( function (results) {
$scope.results = results.data;
});
};
And then in your template use something like:
<button ng-click="getHistory(0, 0)"></button>
{{results}}
Lets see if I can clarify how promises work, and what might be throwing you off here:
When you say:
$scope.results = getResults(value.id);
That means, assign the return value of getResults to the $scope.results variable. Now let us take a look at your getResults function.
var getResults = function(id) {
api.getHistoryData(id)
.then(function (results) {
return results.data;
});
};
Now notice a few things:
The getResults function does not have a return statement. That means there is nothing returned from getResults to be assigned to $scope.results.
The getResults function itself makes an asynchronous request. That means, if when the function executes, and when the results come back are two different points in time.
Finally, your return statement is not inside the getResults function (technically it is), but is in fact inside a function inside the getResults function. Thus, the return results.data is for the function(results) {} and not for getResults
Also, notice if it had worked as you would have expected it to, you would keep overriding the value of $scope.results each time the server responded. That's probably not what you want
Let us modify the getResults function to clarify this:
angular.forEach($scope.orderHistory, function(value) {
console.log('1 - Get results for ', value.id);
getResults(value.id);
});
var getResults = function(id) {
console.log('2 - GetResults called');
api.getHistoryData(id)
.then(function (results) {
console.log('3 - Get results has data');
return results.data;
});
};
If we had code like the above, then the console.logs for 1 and 2 would get printed first, all together, and then at some later point, all the 3's would get printed together. This is because JavaScript is asynchronous and non-blocking, which means it will continue executing without waiting for the responses to come back.
So instead, to get this to work like you expect it to, you need to leverage Promises in AngularJS, which allow you a hook to know when the server response has come back. It offers us a way of working with asynchronous events across functions. So if you modify your code as follows:
angular.forEach($scope.orderHistory, function(value) {
getResults(value.id).then(function(results) {
$scope.results[value.id] = results.data;
});
});
var getResults = function(id) {
return api.getHistoryData(id);
};
So we return a promise from getResults, and then when the promise is completed, we use the value and set it on $scope.results in the main function.
Hope this makes things clearer.
I think you aren't using the promise correctly:
$scope.orderHistory = {};
$scope.results = {};
var getHistory = function (max,offset)
{
//do some stuff
$scope.orderHistory = data.entities;
angular.forEach($scope.orderHistory,function(value)
{
// get results returns a promise
// we use the promise to set $scope.results
getResults(value.id).then(function(results){
$scope.results = results.data;
});
});
}
// returns a promise
var getResults = function(id)
{
return api.getHistoryData(id);
};
$scope.results will be changed only after the promise is resolved.
Also, the way it is currently written, $scope.results will be overwritten for each value.id. If that is not your intention I'd turn it into an array and push each new value into it.
Lets be clear, your orderHistory and results variables are objects, not arrays. The angular.forEach function allows you to iterate over both arrays and object properties; it does not mean the object being iterated is an array. Elad is correct in stating that your results variable will be overwritten for each iteration.
If you do want to keep all the results from your getResults function, you need to instantiate results as an array; and to have your view reflect the results, push them into your scope variable directly from the async result.
$scope.orderHistory = {};
$scope.results = [];
var getHistory = function (max,offset)
{
//do some stuff
$scope.orderHistory = data.entities;
angular.forEach($scope.orderHistory,function(value)
{
getResults(value.id);
});
}
var getResults = function(id)
{
api.getHistoryData(id)
.then ( function (results)
{
$scope.results.push(results);
});
};
just fix getResults, add return to it
var getResults = function(id)
{
return api.getHistoryData(id)
.then ( function (results)
{
return results.data;
});
};
Here are 2 points.
First, getResults returns nothing, it just process chain of promisses. We can change getResults to return promiss which will return result:
var getResults = function(id)
{
return api.getHistoryData(id)
.then ( function (results)
{
return results.data;
});
};
Second, from angular version 1.2 (not exactly sure if from this version) angular do not resolve promisses automaticaly so you must to do it yourself:
angular.forEach($scope.orderHistory,function(value)
{
getResults(value.id).then(function(results){
$scope.results = results;
console.log($scope.results);
})
});
The whole code:
$scope.orderHistory = {};
$scope.results = {};
var getHistory = function (max,offset)
{
//do some stuff
$scope.orderHistory = data.entities;
angular.forEach($scope.orderHistory,function(value)
{
getResults(value.id).then(function(results){
$scope.results = results;
console.log($scope.results);
})
});
}
// returns a promise
var getResults = function(id)
{
return api.getHistoryData(id)
.then ( function (results)
{
return results.data;
});
};