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.
Related
Imagine that you have a factory with a http.get request on initialization, like so:
app.factory('myService', function($http) {
var someArray = [];
$http.get('someUrl').then(function(response) {
someArray = response.data; /* this does not work */
}
getSomeArray = function() {
return someArray;
}
return {
getSomeArray:getSomeArray
}
}
How would you go about saving the respone.data in a correct way?
The reason I would like to know is that I'm assuming that the service is initialized before the controller so sending a promise to the controller would result in a callback to the service to save the variable, requiring an extra call.
As far as I know (and see in the docs), there is no way to initialize factory asynchronously. So it's not guaranteed that your response will be saved to the local variable before it is actually used by external call to getSomeArray().
You should definitely use Promise for this: just trigger the request and save the promise:
app.factory('myService', function ($http) {
var responsePromise = $http.get('someUrl').then(function (response) {
return response.data;
}
return {
getSomeArray: getSomeArray
}
function getSomeArray () {
return responsePromise;
}
}
myService.getSomeArray().then(function (someArray) {
// ...
});
The code below is contrived. I'm simplified things as best I could to ask the question.
I have a simple angular service that makes an API call and returns results:
doWork = function(reqId) {
return $http.get('/api/dowork/' + reqId).then(function(response) {
return response.data;
}).catch(function(response) {
return $q.reject(response.data);
});
}
mediumRequest = function() {
var req = 'medium';//normally do something hard to derive this value
return this.doWork(req);
}
In my controller, I can call the doWork function on the service and get back a good response like this:
myService.doWork('simple').then(function(response){
//do something great with response
});
However, if I need to call an intermediate method to preprocess the request, I get "Cannot read property 'then' of undefined":
myService.mediumRequest().then(function(response){
//do something great with response
});
Why doesn't the function mediumRequest return the promise that doWork returned to it?
Try this code , you did wrong in your service
var app = angular.module("myApp", [])
.service('myService',function($http,$q){
this.doWork = function(reqId) {
return $http.get('/api/dowork/'+ reqId).then(function(response) {
return response.data;
}).catch(function(response) {
return $q.reject(response.data);
});
};
this.mediumRequest = function() {
var req = 'medium';//normally do something hard to derive this value
return this.doWork(req);
};
})
app.controller("myCtrl", function($scope,$compile,myService) {
myService.doWork('simple').then(function(response){
console.log('b',response)
});
myService.mediumRequest().then(function(response){
console.log('a',response)
});
})
It will work
As with most things, the problem was a coding error in my actual service code. The code I've presented in this question would work as expected. I appreciate the couple of folks who offered suggestions of ways to identify the issue. Here was my issue:
My actual "intermediate" function had this structure:
mediumRequest = function(options) {
//process first option meeting criteria
options.forEach(function (item) {
if(item.meetsCriteria)
{
var req = item.code;
return this.doWork(req);
}
});
}
As you can see, the return is actually just exiting the forEach and never actually being returned from the mediumRequest function. Hence the error.
ng.module('app')
.service('CardService', ['$http', CardService])
function CardService($http) {
this.$http = $http;
var self = this;
$http.get('http://localhost:3000/db').success(function(data) {
self.items = data;
console.log(self.items);
});
console.log(self.items);
}
CardService.prototype.list = function() {
console.log(self.items);
return this.items;
};
and result
service.js:14 undefined
service.js:18 undefined
service.js:18 undefined
service.js:12 [Object, Object, Object]
How do I solve this problem?
The ajax call is async. It will allow the thread to move on while it waits for a response from the .get
the console.logs you are doing will then be called in this order.
console.log right below your ajax call
console.log inside list prototype
(inside ajax success)
console.log inside list prototype
console.log below self.items = data
Not the angular way, but... the prototype should used only when the list is initialized inside the ajax call.
According to best practices, you should do all the http request related stuff to get data using factory or service. As it is asynchronous flow, $q shall be used to handle it. So your functionality would be done as given below. Kindly correct me if i have misinterpreted your question.
app.factory('CardService',function($http,$q) {
var obj = {};
obj.getCardServiceData = function(){
var defer = $q.defer();
$http.get('http://localhost:3000/db').then(function(response) {
defer.resolve(response.data);
},function(error){
defer.reject(error);
});
return defer.promise;
}
return obj;
});
app.controller('YOUR CONTROLLER',function($scope,CardService){
CardService.getCardServiceData().then(function(response){
$scope.self = response.data;
console.log($scope.self);
},function(error){
alert("There seems to be some error!");
console.error(error);
});
});
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 want to be able to return pre-loaded data from an angular service if it's already there and if it's not I want to get the data from a web service.
Obviously one of these operations is synchronous and the other isn't so I want to wrap the simple return in an asynchronous way so that I don't have to have lots of if...else blocks in my controllers.
I've tried using the deferred api as follows.
worksOrdersApp.factory("WorksOrderService", function ($http, $q) {
var _jobs = [];
var newWorksOrders = function () {
var deferredJobs = $q.defer();
if (_jobs.length === 0) {
return $http.get('/api/WorksOrders/GetNew').success(function(data) {
deferredJobs.resolve(data);
});
} else {
deferredJobs.resolve(_jobs);
}
return deferredJobs.promise;
};
return {
getNewWorksOrders: newWorksOrders,
};
});
Then in my controller it should be a simple case of calling...
WorksOrderService.getNewWorksOrders().then(function (data) {
$scope.values = data;
});
This initially seems to work, the value of data in the service is an array as I'd expect. However when I inspect the value of data in the controller after the call to deferredJobs.resolve() it's a http response object with details of the status code and a separate data.data property which contains the array. This still means I've got to have if...else code in the controller to check if data is an array or a http response.
How do I just pass the array back to my controller?
Typical, as soon as I ask the question I spot the answer.
In the if (_jobs.length === 0) block I'm returning the $http.get instead of executing it and allowing the promise to return the result.
This does what I expect.
if (_jobs.length === 0) {
$http.get('/api/WorksOrders/GetNew').success(function(data) {
_jobs = data; // I also forgot this line, without it _jobs will always be empty and the else block will never execute
deferredJobs.resolve(data);
});
} else {
deferredJobs.resolve(_jobs);
}