Angular Best Practice: Promise in Factory or Controller?

I have a basic factory in my application that handles API calls. I am currently 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 fulfill the promise like this:

.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 like I could move the processing of promises to my factory instead of doing it in my controller, but I'm not sure if this would have any advantages other than a smaller controller.

Can someone explain the best practices regarding this pattern?

+3


source to share


3 answers


Ultimately, you will be presented with as much data as you want to provide to the service caller. If necessary, you can definitely return an HTTP response object to the caller and handle the response (which, by the way, is always HTTP 2xx if the promise is allowed rather than rejected).

But if you want to isolate the caller from the specifics of how the data was received (perhaps it was cached or provided through 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 dispatcher does not need to worry about the underlying mechanism - all it receives is data or rejection.



.controller('exampleController', function($scope, apiService) {
   apiService.getReports()
     .then(function(reports){
        $scope.reports = reports; // actual reports data
     });
})

      

Off-topic:

Notice how I changed the service name from "apiFactory"

to "apiService"

. I wanted to point this out to eliminate a possible misconception. Whether you use .factory

or .service

or .value

what you get as injecting is always a service instance . .factory

is just the mechanism of how this service is created, so the name "apiFactory"

is incorrect. The only "factory" here is the function you register with .factory

(which can be anonymous, of course):

.factory("fooSvc", function fooSvcFactory(){
   return {
      getFoo: function(){...}
   }
})

      

+10


source


Better to store all data received inside the factory. This allows the controller to free itself from state and it no longer cares about how your factory works. If you change the way you get the data (not using $ http for example), your controller shouldn't care as it just calls getReport () and



Good explanation (see Model Data Resolution, Lack of Callback Argument Binding in Controllers): http://toddmotto.com/rethinking-angular-js-controllers/

+2


source


Short answer: Handle promises in Factory .

Why?

The problems you run into if you handle promises in a controller:

Let's say you have 5 controllers using the same Factory. Now, let's say you want to handle errors when a promise is not resolved correctly. So in the first controller, you write an error callback (or catch (exception) more precisely, as you deal with promises) that shows you the error message. When a promise fails, this controller displays a warning with an error message. So far, so good? Right. But wait! How about the other 4 controllers? You haven't handled errors in them. So now you copy the error handling code from the first controller and paste it into the other 4 controllers.

Now the fun begins. Imagine you want to change your logic in an error state. Maybe you just want to log a bug to the console or show a message with a toaster. So you go to the first controller and update your code. Do you think you're done? NOT!!! There are 4 more controllers that show a warning message (remember you pasted the code from the first controller earlier ???). So now you are trying to update the rest of the controllers by inserting new error message logic. Think of all the controllers you need to update if you have any more !!! Tiring, isn't it?

Why solve a promise in Factory:

Now let's say that you write your error handling logic in your service and all controllers use this service. You have written code to show a warning in case of a promise that does not work in this service. All controllers will now start showing this warning if a promise is broken. In the future, if you want to update your error logic, you simply update this error logic in your service, and all your controllers will automatically start using this updated logic without any additional effort on your part. Thus, you have saved a ton of time.

So think about it:

1 change in Factory vs. 1 change in all controllers (possibly 1 or more)

I would definitely vouch for 1 change to Factory as it helps to reuse my logic with a simple change and only one time. All my controllers will start using the new logic. Controllers should be lean, lean, and clean. Factories and services are expected to be reused. For this reason of reuse, I highly recommend that you declare the descriptor in Factory / Service .

+2


source







All Articles