I have a basic factory in my app that handles API calls. Currently I'm using the form:
.factory('apiFactory', function($http){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
return result;
});
},
getReport: function(id) {
return $http.get(url+'report/'+id).then(function(result) {
return result;
});
}
}
})
And in my controller I'm handling the promise like so:
.controller('exampleController', function($scope, apiFactory) {
apiFactory.getReports().then(
function(answer) {
if (answer.status==200){
if (answer.data.status == "error"){
// DISPLAY ERROR MESSAGE
console.log(answer.data.msg);
}
} else{
// THROW error
console.log('error: ', answer);
}
},
function(error){
console.log('error: ', answer);
}
);
}
}
})
It seems I could move the promise handling to my Factory instead of doing it in my controller, but I'm not sure if that would have any benefits others than a smaller controller.
Could somebody explain the best practices regarding this pattern?
I have a basic factory in my app that handles API calls. Currently I'm using the form:
.factory('apiFactory', function($http){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
return result;
});
},
getReport: function(id) {
return $http.get(url+'report/'+id).then(function(result) {
return result;
});
}
}
})
And in my controller I'm handling the promise like so:
.controller('exampleController', function($scope, apiFactory) {
apiFactory.getReports().then(
function(answer) {
if (answer.status==200){
if (answer.data.status == "error"){
// DISPLAY ERROR MESSAGE
console.log(answer.data.msg);
}
} else{
// THROW error
console.log('error: ', answer);
}
},
function(error){
console.log('error: ', answer);
}
);
}
}
})
It seems I could move the promise handling to my Factory instead of doing it in my controller, but I'm not sure if that would have any benefits others than a smaller controller.
Could somebody explain the best practices regarding this pattern?
Share Improve this question edited May 5, 2015 at 11:16 Artur Peniche 4796 silver badges28 bronze badges asked May 5, 2015 at 11:13 SqurlerSqurler 3,5148 gold badges46 silver badges67 bronze badges 3- How would the factory know how to display an error message? – Sacho Commented May 5, 2015 at 11:39
- It seems as though maybe the checking for status codes don't belong in the controller but instead in the factory....? I don't know for sure, it's why I ask :) – Squrler Commented May 5, 2015 at 11:41
- Okay, but what is the factory supposed to do after it checks the error code? – Sacho Commented May 5, 2015 at 11:42
3 Answers
Reset to default 10It is ultimately up to you how much data you want to provide to the caller of the service. If needed, you could definitely return the HTTP response object to the caller, and have them process the response (which, btw, is always HTTP 2xx, if the promise is resolved rather than rejected).
But if you want to isolate the caller from the specifics of how the data got there (maybe it was cached, or supplied via another mechanism), and if you need to post-process the data, then it is advisable to handle the response in the service.
Here's an example:
.factory("apiService", function($http, $q){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
var data = result.data;
if (data === "something I don't accept"){
return $q.reject("Invalid data");
}
var processedData = processData(data);
return processedData;
})
.catch(function(err){
// for example, "re-throw" to "hide" HTTP specifics
return $q.reject("Data not available");
})
},
// same idea for getReport
}
});
Then the controller wouldn't need to care about the underlying mechanism - all it gets is data or a rejection.
.controller('exampleController', function($scope, apiService) {
apiService.getReports()
.then(function(reports){
$scope.reports = reports; // actual reports data
});
})
Off-topic:
Notice how I changed the name of the service from "apiFactory"
to "apiService"
. I wanted to point that out to remove a possible misconception. Whether you use .factory
or .service
or .value
what you get as an injectable is always a service instance. .factory
is just a mechanism of how this service is instantiated, so the name "apiFactory"
is a misnomer. The only "factory" here is a function that you register with .factory
(which could be anonymous, of course):
.factory("fooSvc", function fooSvcFactory(){
return {
getFoo: function(){...}
}
})
Better to keep all the data fetching inside the factory. This keeps the controller free from state, and it no longer cares how your factory works. If you change how you get data (e.g. not using $http) your controller shouldn't care, as it just calls getReport() and
A good explanation (see "Resolving Model data, no callback arg binding in Controllers"): http://toddmotto./rethinking-angular-js-controllers/
Short answer: Handle promises in Factory.
Why?
Problems you face if you handle promises in Controller:
Let's say you have 5 Controllers that use the same Factory. Now let's say that you want to handle the errors when the promise does not get resolved correctly. So in the first controller, you write an error callback (or the catch(exception) more precisely, as you are dealing with promises), that shows you an alert message with the error. When the promise fails, this controller shows an alert with the error message. So far, so good? Right. But wait! What about the other 4 controllers? You haven't handled the errors in them. So now you end up copying the error handling code from the first controller & pasting it in the rest of the 4 controllers.
Now the fun starts. Imagine that you want to change your logic in the error state. Maybe you want to just log the error in console, or show a toaster message perhaps. So you go to the first controller & update the code. You think you are done? NO!!! There are 4 other controllers that are showing the alert message (remember that you pasted the code from the first controller previously???). So now, you try to update the rest of the controllers by pasting the new error message logic. Think about all the controllers that you need to update, if you have more!!! Tiring, isn't it???
Why to resolve promise in Factory:
Now let's say that you write your error handling logic in your service and all the controllers are using this service. You have written code to show an alert in case of a promise that fails in this service. All controllers will now start showing this alert, in case of a broken promise. In future, if you wish to update your error logic, you simply update this error logic in your service & all your controllers will start using this updated logic automatically, without any more effort on your part. This way, you have saved yourself tons of time.
So think about this:
1 change in Factory vs 1 change in all Controllers (which maybe 1 or more)
I would definitely vouch for 1 change in Factory as that helps reuse my logic with just a simple change and only once. All my controllers will start using the new logic. Controllers are supposed to be slim, lean & clean. Factories & Services are supposed to be reusable. For this reason of reusability, I strongly suggest you handle promise in Factory/Service.