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.
Related
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
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'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;
});
};
I have the following Angular file, where I try to access a database with $http, and then use this data in a $scope variable (to display in the webpage). The trouble is I can't get $q.defer to run as I believe it should. The console.log() inside the $http function logs an object containing the data returned from the database. However when I call the function it logs Object {then: function}. The data is contained within this object, but it is not the only part of the object. (It seems like it's in Object.$$v. I'm not sure what that means.)
var app = angular.module("app", []);
app.factory('portfolioFactory', function ($http, $q) {
var obj = {};
obj.getResponse = function(){
var deferred = $q.defer();
$http.get('./../includes/portfolio/db_access.php').success(function(data){
deferred.resolve(data);
console.log(data);
});
//console.log(deferred.promise);
return deferred.promise;
}
return obj;
});
app.controller("PortfolioCtrl", function($scope, portfolioFactory) {
$scope.PortfolioItems = portfolioFactory.getResponse();
console.log($scope.PortfolioItems);
});
$http.get( ... );
Returns a promise.
app.controller("PortfolioCtrl", function($scope, portfolioFactory) {
portfolioFactory.getResponse().then(function(response) {
$scope.PortfolioItems = response.data;
})
});
Will work.
In older versions of Angular $scope.PortfolioItems = portfolioFactory.getResponse();
would have worked too, but in newer versions Angular does not automatically unwrap promises anymore.
A promise is basically an alternate and nicer way of handling async action (a design pattern).
Rather than using callbacks in a regular fashion you can put your callbacks in a queue with promise.then( callback ). Then whenever the deferred.resolve method is called, the callbacks are called with the results. If the promise already is resolved before the callbacks where queued they are immediately called with the cached data. Much nicer than doing query( callback ) code that quite often turns into callback hell.
FYI you could rather do this, as the result of any $http call is a promise already:
app.factory('portfolioFactory', function ($http) {
var obj = {};
obj.getResponse = function(){
return $http.get('./../includes/portfolio/db_access.php').then(function(data){
console.log(data);
return data; //Return data further up the chain.
});
}
return obj;
});
You should be able to invoke the "then" function to get to your data.
app.controller("PortfolioCtrl", function($scope, portfolioFactory) {
$scope.PortfolioItems = portfolioFactory.getResponse();
$scope.PortfolioItems.then(function(results){
console.log(results);
};
});
It might spell it out to label things like this....
app.controller("PortfolioCtrl", function($scope, portfolioFactory) {
$scope.PortfolioItems = null;
var fetchPortfolioItems = portfolioFactory.getResponse();
fetchPortfolioItems.then(function(results){
$scope.PortfolioItems = results;
console.log($scope.PortfolioItems);
};
});