Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular - Best practice for retrieving data from a Factory method

I'm looking for some information on the best way to retrieve data from a local JSON file and handle the response. After browsing through Stack Overflow, I have some mixed thoughts as I've seen multiple ways of doing the same thing (although no explanation on why one may or may not be preferred).

Essentially, I have an Angular app that is utilising a factory to retrieve data from a JSON file; I'm then waiting for the response to resolve in my controller before using it in my html file, similar to the below:

Option 1

Factory:

comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';

return {
 retrieveInfo: function() {
  return $http.get(retrievalFile);
 }
}

}]);

Controller:

comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {

Info.retrieveInfo().then(function(response) {
  $scope.info = response.data;
});

}]);

My main point of contention is figuring out when it's best to wait for the response to resolve, or if it even matters. I'm toying with the idea of having the factory return the fulfilled promise, and wait for the controller to retrieve the data also. In my view, it's best to abstract all data retrieval out of the controller and into the factory, but I'm not sure if this extends to waiting for the actual data to be returned within the factory itself. With this in mind, I'm confused about whether to opt for option 1 or option 2 and would really appreciate some feedback from more experienced/qualified developers!

Option 2

Factory:

comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';

return {
  retrieveInfo: function() {
    return $http.get(retrievalFile).then(function(response) {
      return response.data;
    });
  }
}

}]);

Controller:

comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {

Info.retrieveInfo().then(function(response) {
  $scope.info = response;
});

}]);

Thank you for any input/suggestions in advance!

like image 806
Alaan Avatar asked Jan 27 '16 02:01

Alaan


3 Answers

It depends on what your controller is expecting and how you set up your application. Generally, I always go with the second option. Its because I usually have global error or success handlers in all api requests and I have a shared api service. Something like below.

var app = angular.module('app', []);

app.service('ApiService', ['$http', function($http) {
    var get = function(url, params) {
    $http.get(url, { params: params })
        .then(handleSuccess, handleError);
  };

  // handle your global errors here
  // implementation will vary based upon how you handle error
  var handleError = function(response) {
    return $q.reject(response);
  };

  // handle your success here
  // you can return response.data or response based upon what you want
  var handleSuccess = function(response) {
    return response.data;
  };
}]);

app.service('InfoService', ['ApiService', function(ApiService) {
    var retrieveInfo = function() {
    return ApiService.get(retrievalFile);

    /**
    // or return custom object that your controller is expecting
    return ApiService.get.then(function(data) {
      return new Person(data);
    });
    **//
  };

  // I prefer returning public functions this way
  // as I can just scroll down to the bottom of service 
  // to see all public functions at one place rather than
  // to scroll through the large file
  return { retrieveInfo: retrieveInfo };
}]);

app.controller('InfoController', ['InfoService', function(InfoService) {
  InfoService.retrieveInfo().then(function(info) {
    $scope.info = info;
  });
}])

Or if you are using router you can resolve the data into the controller. Both ngRouter and uiRouter support resolves:

$stateProvider.state({
    name: 'info',
  url: '/info',
  controller: 'InfoController',
  template: 'some template',
  resolve: {
    // this injects a variable called info in your controller
    // with a resolved promise that you return here
    info: ['InfoService', function(InfoService) {
        return InfoService.retrieveInfo();
    }]
  }
});

// and your controller will be like
// much cleaner right
app.controller('InfoController', ['info', function(info) {
    $scope.info = info;
}]);
like image 154
Subash Avatar answered Oct 20 '22 17:10

Subash


It's really just preference. I like to think of it in terms of API. What is the API you want to expose? Do you want your controller to receive the entire response or do you want your controller to just have the data the response wraps? If you're only ever going to use response.data then option 2 works great as you never have to deal with anything but the data you're interested in.

A good example is the app we just wrote where I work. We have two apps: a back-end API and our front-end Angular application. We created an API wrapper service in the front-end application. In the service itself we place a .catch for any of the API endpoints that have documented error codes (we used Swagger to document and define our API). In that .catch we handle those error codes and return a proper error. When our controllers/directives consume the service they get back a much stricter set of data. If an error occurs then the UI is usually safe to just display the error message sent from the wrapper service and won't have to worry about looking at error codes.

Likewise for successful responses we do much of what you're doing in option 2. In many cases we refine the data down to what is minimally useful in the actual app. In this way we keep a lot of the data churning and formatting in the service and the rest of the app has a lot less to do. For instance, if we need to create an object based on that data we'll just do that in return the object to the promise chain so that controllers aren't doing that all over the place.

like image 2
Chev Avatar answered Oct 20 '22 15:10

Chev


I would choose option two, as it your options are really mostly the same. But let see when we add a model structure like a Person suppose.

comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';

return {
  retrieveInfo: function() {
    return $http.get(retrievalFile).then(function(response) {
      //we will return a Person...
      var data = response.data;
      return new Person(data.name, data.age, data.gender);
    });
  }
}

}]);

This is really simple, but if you have to map more complex data into object models (you retrieve a list of people with their own items... etc), that's when things get more complicated, you will probably want to add a service to handle the mapping between data and models. Well you have another service DataMapper(example), if you choose your first option you will have to inject DataMapper into your controller and you will have to make your request through your factory, and map the response with the injected service. And then you probably say, Should I have all this code here? ... Well probably no.

That is an hypothetical case, something that count a lot is how you feel structuring your code, won't architecture it in a way you won't understand. And at the end take a look at this: https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) and research more information about this principles but focused to javascript.

like image 1
Miguel Lattuada Avatar answered Oct 20 '22 15:10

Miguel Lattuada