I'm using Angular 1.5.8. The views in my app require different combinations of the same 3 ajax requests. Some views require data from all three, others require data from two, or even one single endpoint.
I'm working on a function that will manage the retrieval of this data, requiring the app to only call each endpoint once. I want the ajax requests to be called as needed, but only when needed. Currently I've created a function which works, but seems like it could use improvement.
The following function is contained within the $rootScope. It uses the fetchData() function to cycle through the get requests as requested. When data is retrieved, it is stored in the global variable $rootScope.appData and then fetchData() is called again. When all data is retrieved the deferred promise is resolved and the data is returned to the controller.
$rootScope.appData = {};
$rootScope.loadAppData = function(fetch) {
var deferred = $q.defer();
function getUser() {
$http
.get('https://example.com/api/getUser')
.success(function(result){
$rootScope.appData.currentUser = result;
fetchData();
});
}
function getPricing() {
$http
.get('https://example.com/api/getPricing')
.success(function(result) {
$rootScope.appData.pricing = result;
fetchData();
});
}
function getBilling() {
$http
.get('https://example.com/api/getBilling')
.success(function(result) {
$rootScope.appData.billing = result;
fetchData();
});
}
function fetchData() {
if (fetch.user && !$rootScope.appData.currentUser) {
getUser();
} else if (fetch.pricing && !$rootScope.appData.pricing) {
getPricing();
} else if (fetch.billing && !$rootScope.appData.billing) {
getBilling();
} else {
deferred.resolve($rootScope.appData);
}
}
if ($rootScope.appData.currentUser && $rootScope.appData.pricing &&$rootScope.appData.billing) {
deferred.resolve($rootScope.appData);
} else {
fetchData();
}
return deferred.promise;
};
An object fetch is submitted as an attribute, this object shows which ajax requests to call. An example call to the $rootScope.loadAppData() where only user and pricing data would be requested would look like this:
$rootScope.loadAppData({user: true, pricing: true}).then(function(data){
//execute view logic.
});
I'm wondering:
Should the chaining of these functions be done differently? Is the fetchData() function sufficient, or is this an odd way to execute this functionality?
Is there a way to call all needed Ajax requests simultaneously, but wait for all required calls to complete before resolving the promise?
Is it unusual to store data like this in the $rootScope?
I'm aware that this function is not currently handling errors properly. This is functionality I will add before using this snippet, but isn't relevant to my question.
Instead of using the .success method, use the .then method and return data to its success handler:
function getUserPromise() {
var promise = $http
.get('https://example.com/api/getUser')
.then( function successHandler(result) {
//return data for chaining
return result.data;
});
return promise;
}
Use a service instead of $rootScope:
app.service("myService", function($q, $http) {
this.loadAppData = function(fetchOptions) {
//Create first promise
var promise = $q.when({});
//Chain from promise
var p2 = promise.then(function(appData) {
if (!fetchOptions.user) {
return appData;
} else {
var derivedPromise = getUserPromise()
.then(function(user) {
appData.user = user;
//return data for chaining
return appData;
});
return derivedPromise;
);
});
//chain from p2
var p3 = p2.then(function(appData) {
if (!fetchOptions.pricing) {
return appData;
} else {
var derivedPromise = getPricingPromise()
.then(function(pricing) {
appData.pricing = pricing;
//return data for chaining
return appData;
});
return derivedPromise;
);
});
//chain from p3
var p4 = p3.then(function(appData) {
if (!fetchOptions.billing) {
return appData;
} else {
var derivedPromise = getBillingPromise()
.then(function(user) {
appData.billing = billing;
//return data for chaining
return appData;
});
return derivedPromise;
);
});
//return final promise
return p4;
}
});
The above example creates a promise for an empty object. It then chains three operations. Each operations checks to see if a fetch is necessary. If needed a fetch is executed and the result is attached to the appData object; if no fetch is needed the appData object is passed to the next operation in the chain.
USAGE:
myService.loadAppData({user: true, pricing: true})
.then(function(appData){
//execute view logic.
}).catch(functon rejectHandler(errorResponse) {
console.log(errorResponse);
throw errorResponse;
});
If any of the fetch operations fail, subsequent operations in the chain will be skipped and the final reject handler will be called.
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs. -- AngularJS $q Service API Reference - Chaining Promises
Found a good way to answer question 2 in the original post. Using $q.all() allows the promises to execute simultaneously, resolving once they all complete, or failing as soon as one of them fails. I've added this logic into a service thanks to #georgeawg. Here's my re-write putting this code into a service, and running all calls at the same time:
services.factory('appData', function($http, $q) {
var appData = {};
var coreData = {};
appData.loadAppData = function(fetch) {
var deferred = $q.defer();
var getUser = $q.defer();
var getPricing = $q.defer();
var getBilling = $q.defer();
if (!fetch.user || coreData.currentUser) {
getUser.resolve();
} else {
$http
.get('https://example.com/api/getUser')
.success(function(result){
coreData.currentUser = result;
getUser.resolve();
}).error(function(reason) {
getUser.reject(reason);
});
}
if (!fetch.billing || coreData.billing) {
getBilling.resolve();
} else {
$http
.get('https://example.com/api/getBilling')
.success(function(result) {
coreData.billing = result;
getBilling.resolve();
}).error(function(reason) {
getBilling.reject(reason);
});
}
if (!fetch.pricing || coreData.pricing) {
getPricing.resolve();
} else {
$http
.get('https://example.com/api/getPricing')
.success(function(result) {
coreData.pricing = result;
getPricing.resolve();
}).error(function(reason) {
getPricing.reject(reason);
});
}
$q.all([getPricing.promise, getUser.promise, getBilling.promise]).then(function(result) {
deferred.resolve(coreData);
}, function(reason){
deferred.reject(reason);
});
return deferred.promise;
};
return appData;
});
I want to load a JSON file into a factory and then return its value.
Here's the code:
angular.module('getGuilds', [])
.factory('getGuilds', getGuilds);
getGuilds.$inject = ['$http'];
function getGuilds($http){
var obj = {content:null};
$http.get('guild/guilds.json').success(function(data) {
obj.content = data;
});
return obj;
}
The problem is that it only returns the object with the value of null, so it seems the $http.get doesn't change the value of the obj.content.
After this I did a little test:
$http.get('guild/guilds.json').success(function(data) {
obj.content = data;
});
console.log(obj)
return obj;
}
It gave back this object insted of the JSON's array: {content:null}.
Then I put the console.log inside the $http.get request.
$http.get('guild/guilds.json').success(function(data) {
obj.content = data;
console.log(obj)
});
Guess what, it logged out the JSON file. Would somebody be so kind as to help me?
$http.get performs the get asynchronously. The code that appears after the success block in the source runs before the code within the success block. So it's behaving just as expected: on your first attempt, you're logging the results of an unfinished request.
This is because $http.get performs the get asynchronously. So at the point you were assigning the value, it wasn't available yet. You need to return the obj inside your success function changing your code like this should make it work:
// dataservice factory
angular
.module('getGuilds', [])
.factory('dataservice', dataservice);
dataservice.$inject = ['$http'];
function dataservice($http) {
return {
getGuilds: getGuilds
};
function getGuilds() {
return $http.get('guild/guilds.json')
.then(getGuildsComplete)
.catch(getGuildsFailed);
function getGuildsComplete(response) {
return response.data;
}
function getGuildsFailed(error) {
console.log('XHR Failed for getGuilds.' + error.data);
}
}
}
In your controller you would then call the dataservice:
dataservice.getGuilds()
.then(function(data) {
vm.guilds = data;
return vm.guilds;
});
Obviously the controller requires more code to actually work. But this should give you enough info to solve your issue.
I'm trying to store some user data in a service that will be accessible and modified across different controllers, and the data will be initially pulled via a $http call. Since the data has to be loaded, I've used promises in my previous code, but this can be quite irritating to write. For instance, even a basic getter function has to be written as the following
UserData.getData().then(function(data) {
//do something with the data
})
where UserData.getData() always returns a promise via deferred.promise (if the data has already been pulled, then resolve immediately). I'm wondering if there is anyway to split a) $http calls, and b) getter and setter methods into two different services so that if I call getter and setter methods from b), I don't need to wrap everything with then?
For instance, I'm trying to make UserFactory in charge of the $http call, and UserData in charge of getters and setters. However, I can't get the code to work since UserData.getData() will return undefined, and wondering if anyone can help? (I don't really want to have to use then everywhere).
angular.module('testApp', [])
//mocks a service that gets data from a server
.factory('UserFactory', function($timeout, $q) {
return {
getData: function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve({title: 'hello world'});
}, 1000);
return deferred.promise;
}
}
})
.factory('UserData', function(UserFactory) {
var data;
return {
//if already pulled, use existing data
getData: function() {
if (data) return data;
UserFactory.getData().then(function(res) {
data = res;
return data;
})
}
}
})
http://jsfiddle.net/QNLk2/1/
The then method is executed asynchronously so the return method as no effect there. That is why until the data has arrived every call to getData will return undefined.
You can try the following approach, where you can specify a callback if you want to be notified when data is ready, or simply wait for the data to be populated in the return variable.
.factory('UserData', function(UserFactory) {
var data = {};
return {
getData: function(callback) {
UserFactory.getData().then(function(res) {
angular.extend(data, res);
if (callback) {
callback(data);
}
});
return data;
}
}
})
I am trying to build a factory to act as a staging area for my database models, as well as an api to perform basic CRUD calls. I want to be able to access data by storing it in a service or a factory, and keep api methods along with it so I can perform actions like these in the controller.
$scope.folders = Folders.data(); // for a factory
$scope.folders = Folders.data; // for a Service
Folders.create({name: "My Stuff, $oid: { 5fwewe033333 }, user_id: CurrentUser.id"});
Currently I am using the Folder factory like this in the controller.
Folders.foldersData().success( function(data, status) {
$scope.folder = data;
})
.error( function(data,status) {
Flash.warning("There was a problem fetching your data");
});
I know I can just have a promise resolved in the controller, but with the size of the project I'm working on, I like the idea of accessing the Folders model in a service, with out having to make a server call to sync the data every time I make a change.
angular.module('cmsApp')
.factory('Folders', function($http, $q){
var folders = {};
var messageWarn = "Upload Retrival Failed.";
return {
get: function(){
var defered = $q.defer();
$http.get('/folders').success( function ( data, status ) {
defered.resolve(data);
})
.error( function ( data, status ) {
defered.reject();
Flash.warning(message_warn);
});
defered.promise.then( function (promise)
folders = promise;
});
},
data: function (){
return folders;
},
}
});
My problem is that I can't keep the folders object to persist after I call Folders.get(). It always comes back after I call Folders.data() as an empty object.
Is there a way to keep this data stored in the Factory as a up-to-date representation of the Folder model that is not dependent on hitting the server every time?
Running angular 1.2.3, on a Rails 4 API.
You can store the promise in the service as an object on the service. I forked the expanded demo above to demonstrate http://plnkr.co/edit/2HqQAiD33myyfVP4DWg3?p=preview
As with the previous examples, the http call is only made once but this time the promise is added to the folders item on the service object which gets created by the factory.
app.factory('myService', function($http, $q) {
return {
myObject: '',
get: function() {
// Create the deffered object
var deferred = $q.defer();
if(!this.myObject) {
// Request has not been made, so make it
$http.get('my-file.json').then(function(resp) {
console.log('Making the call!');
deferred.resolve(resp.data);
});
// Add the promise to myObject
this.myObject = deferred.promise;
}
// Return the myObject stored on the service
return this.myObject;
}
};
});
In this example, the service essentially IS the data. The first time the service is injected, a promise is created and the call goes out. The service is actually the promise of that data, but when the call comes back, the promise is resolved with the data. So, the second, third, etc time the service is injected, the call isn't made again - the factory has already done its job and returned the service (the promise in this example).
Live demo (click).
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
myService.then(function(data) {
$scope.data = data
})
});
app.factory('myService', function($http, $q) {
//this runs the first time the service is injected
//this creates the service
var promise = $http.get('my-file.json').then(function(resp) {
return resp.data;
});
return promise;
});
Here's an expanded demo in which I use the data twice. Note the console log - the call is only ever made once. In this demo, I made the service into an object with a get method that returns the promise. I just wanted to demonstrate another way this technique could be implemented.
app.factory('myService', function($http, $q) {
console.log('Making the call!');
var promise = $http.get('my-file.json').then(function(resp) {
return resp.data;
});
var myService = {
get: function() {
return promise;
}
};
return myService;
});
I recently posted a detailed description of the issue I am facing here at SO. As I couldn't send an actual $http request, I used timeout to simulate asynchronous behavior. Data binding from my model to view is working correct, with the help of #Gloopy
Now, when I use $http instead of $timeout (tested locally), I could see the asynchronous request was successful and data is filled with json response in my service. But, my view is not updating.
updated Plunkr here
Here is a Plunk that does what you want: http://plnkr.co/edit/TTlbSv?p=preview
The idea is that you work with promises directly and their "then" functions to manipulate and access the asynchronously returned responses.
app.factory('myService', function($http) {
var myService = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('test.json').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return myService;
});
app.controller('MainCtrl', function( myService,$scope) {
// Call the async method and then do stuff with what is returned inside our own then function
myService.async().then(function(d) {
$scope.data = d;
});
});
Here is a slightly more complicated version that caches the request so you only make it first time (http://plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview):
app.factory('myService', function($http) {
var promise;
var myService = {
async: function() {
if ( !promise ) {
// $http returns a promise, which has a then function, which also returns a promise
promise = $http.get('test.json').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
}
// Return the promise to the controller
return promise;
}
};
return myService;
});
app.controller('MainCtrl', function( myService,$scope) {
$scope.clearData = function() {
$scope.data = {};
};
$scope.getData = function() {
// Call the async method and then do stuff with what is returned inside our own then function
myService.async().then(function(d) {
$scope.data = d;
});
};
});
Let it be simple. It's as simple as
Return promise in your service(no need to use then in service)
Use then in your controller
Demo. http://plnkr.co/edit/cbdG5p?p=preview
var app = angular.module('plunker', []);
app.factory('myService', function($http) {
return {
async: function() {
return $http.get('test.json'); //1. this returns promise
}
};
});
app.controller('MainCtrl', function( myService,$scope) {
myService.async().then(function(d) { //2. so you can use .then()
$scope.data = d;
});
});
Because it is asynchronous, the $scope is getting the data before the ajax call is complete.
You could use $q in your service to create promise and give it back to
controller, and controller obtain the result within then() call against promise.
In your service,
app.factory('myService', function($http, $q) {
var deffered = $q.defer();
var data = [];
var myService = {};
myService.async = function() {
$http.get('test.json')
.success(function (d) {
data = d;
console.log(d);
deffered.resolve();
});
return deffered.promise;
};
myService.data = function() { return data; };
return myService;
});
Then, in your controller:
app.controller('MainCtrl', function( myService,$scope) {
myService.async().then(function() {
$scope.data = myService.data();
});
});
tosh shimayama have a solution but you can simplify a lot if you use the fact that $http returns promises and that promises can return a value:
app.factory('myService', function($http, $q) {
myService.async = function() {
return $http.get('test.json')
.then(function (response) {
var data = reponse.data;
console.log(data);
return data;
});
};
return myService;
});
app.controller('MainCtrl', function( myService,$scope) {
$scope.asyncData = myService.async();
$scope.$watch('asyncData', function(asyncData) {
if(angular.isDefined(asyncData)) {
// Do something with the returned data, angular handle promises fine, you don't have to reassign the value to the scope if you just want to use it with angular directives
}
});
});
A little demonstration in coffeescript: http://plunker.no.de/edit/ksnErx?live=preview
Your plunker updated with my method: http://plnkr.co/edit/mwSZGK?p=preview
A much better way I think would be something like this:
Service:
app.service('FruitsManager',function($q){
function getAllFruits(){
var deferred = $q.defer();
...
// somewhere here use: deferred.resolve(awesomeFruits);
...
return deferred.promise;
}
return{
getAllFruits:getAllFruits
}
});
And in the controller you can simply use:
$scope.fruits = FruitsManager.getAllFruits();
Angular will automatically put the resolved awesomeFruits into the $scope.fruits.
I had the same problem, but when I was surfing on the internet I understood that $http return back by default a promise, then I could use it with "then" after return the "data". look at the code:
app.service('myService', function($http) {
this.getData = function(){
var myResponseData = $http.get('test.json').then(function (response) {
console.log(response);.
return response.data;
});
return myResponseData;
}
});
app.controller('MainCtrl', function( myService, $scope) {
// Call the getData and set the response "data" in your scope.
myService.getData.then(function(myReponseData) {
$scope.data = myReponseData;
});
});
When binding the UI to your array you'll want to make sure you update that same array directly by setting the length to 0 and pushing the data into the array.
Instead of this (which set a different array reference to data which your UI won't know about):
myService.async = function() {
$http.get('test.json')
.success(function (d) {
data = d;
});
};
try this:
myService.async = function() {
$http.get('test.json')
.success(function (d) {
data.length = 0;
for(var i = 0; i < d.length; i++){
data.push(d[i]);
}
});
};
Here is a fiddle that shows the difference between setting a new array vs emptying and adding to an existing one. I couldn't get your plnkr working but hopefully this works for you!
Related to this I went through a similar problem, but not with get or post made by Angular but with an extension made by a 3rd party (in my case Chrome Extension).
The problem that I faced is that the Chrome Extension won't return then() so I was unable to do it the way in the solution above but the result is still Asynchronous.
So my solution is to create a service and to proceed to a callback
app.service('cookieInfoService', function() {
this.getInfo = function(callback) {
var model = {};
chrome.cookies.get({url:serverUrl, name:'userId'}, function (response) {
model.response= response;
callback(model);
});
};
});
Then in my controller
app.controller("MyCtrl", function ($scope, cookieInfoService) {
cookieInfoService.getInfo(function (info) {
console.log(info);
});
});
Hope this can help others getting the same issue.
I've read http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/
[AngularJS allows us to streamline our controller logic by placing a promise directly on the scope, rather than manually handing the resolved value in a success callback.]
so simply and handy :)
var app = angular.module('myApp', []);
app.factory('Data', function($http,$q) {
return {
getData : function(){
var deferred = $q.defer();
var promise = $http.get('./largeLoad').success(function (response) {
deferred.resolve(response);
});
// Return the promise to the controller
return deferred.promise;
}
}
});
app.controller('FetchCtrl',function($scope,Data){
$scope.items = Data.getData();
});
Hope this help
I really don't like the fact that, because of the "promise" way of doing things, the consumer of the service that uses $http has to "know" about how to unpack the response.
I just want to call something and get the data out, similar to the old $scope.items = Data.getData(); way, which is now deprecated.
I tried for a while and didn't come up with a perfect solution, but here's my best shot (Plunker). It may be useful to someone.
app.factory('myService', function($http) {
var _data; // cache data rather than promise
var myService = {};
myService.getData = function(obj) {
if(!_data) {
$http.get('test.json').then(function(result){
_data = result.data;
console.log(_data); // prove that it executes once
angular.extend(obj, _data);
});
} else {
angular.extend(obj, _data);
}
};
return myService;
});
Then controller:
app.controller('MainCtrl', function( myService,$scope) {
$scope.clearData = function() {
$scope.data = Object.create(null);
};
$scope.getData = function() {
$scope.clearData(); // also important: need to prepare input to getData as an object
myService.getData($scope.data); // **important bit** pass in object you want to augment
};
});
Flaws I can already spot are
You have to pass in the object which you want the data added to, which isn't an intuitive or common pattern in Angular
getData can only accept the obj parameter in the form of an object (although it could also accept an array), which won't be a problem for many applications, but it's a sore limitation
You have to prepare the input object $scope.data with = {} to make it an object (essentially what $scope.clearData() does above), or = [] for an array, or it won't work (we're already having to assume something about what data is coming). I tried to do this preparation step IN getData, but no luck.
Nevertheless, it provides a pattern which removes controller "promise unwrap" boilerplate, and might be useful in cases when you want to use certain data obtained from $http in more than one place while keeping it DRY.
As far as caching the response in service is concerned , here's another version that seems more straight forward than what I've seen so far:
App.factory('dataStorage', function($http) {
var dataStorage;//storage for cache
return (function() {
// if dataStorage exists returned cached version
return dataStorage = dataStorage || $http({
url: 'your.json',
method: 'GET',
cache: true
}).then(function (response) {
console.log('if storage don\'t exist : ' + response);
return response;
});
})();
});
this service will return either the cached data or $http.get;
dataStorage.then(function(data) {
$scope.data = data;
},function(e){
console.log('err: ' + e);
});
Please try the below Code
You can split the controller (PageCtrl) and service (dataService)
'use strict';
(function () {
angular.module('myApp')
.controller('pageContl', ['$scope', 'dataService', PageContl])
.service('dataService', ['$q', '$http', DataService]);
function DataService($q, $http){
this.$q = $q;
this.$http = $http;
//... blob blob
}
DataService.prototype = {
getSearchData: function () {
var deferred = this.$q.defer(); //initiating promise
this.$http({
method: 'POST',//GET
url: 'test.json',
headers: { 'Content-Type': 'application/json' }
}).then(function(result) {
deferred.resolve(result.data);
},function (error) {
deferred.reject(error);
});
return deferred.promise;
},
getABCDATA: function () {
}
};
function PageContl($scope, dataService) {
this.$scope = $scope;
this.dataService = dataService; //injecting service Dependency in ctrl
this.pageData = {}; //or [];
}
PageContl.prototype = {
searchData: function () {
var self = this; //we can't access 'this' of parent fn from callback or inner function, that's why assigning in temp variable
this.dataService.getSearchData().then(function (data) {
self.searchData = data;
});
}
}
}());