7

Currently I have calls like this all over my three controllers:

$scope.getCurrentUser = function () {
    $http.post("/Account/CurrentUser", {}, postOptions)
        .then(function(data) {
                var result = angular.fromJson(data.data);
                if (result != null) {
                    $scope.currentUser = result.id;
                }
            },
            function(data) {
                alert("Browser failed to get current user.");
            });
};

I see lots of advice to encapsulate the $http calls into an HttpService, or some such, but that it is much better practice to return the promise than return the data. Yet if I return the promise, all but one line in my controller $http call changes, and all the logic of dealing with the response remains in my controllers, e.g:

$scope.getCurrentUser = function() {
    RestService.post("/Account/CurrentUser", {}, postOptions)
        .then(function(data) {
                var result = angular.fromJson(data.data);
                if (result != null) {
                    $scope.currentUser = result.id;
                }
            },
            function(data) {
                alert("Browser failed to get current user.");
            });
};

I could create a RestService for each server side controller, but that would only end up calling a core service and passing the URL anyway.

ProfK
  • 49,207
  • 121
  • 399
  • 775
  • 1
    possible duplicate http://stackoverflow.com/questions/17646034/what-is-the-best-practice-for-making-an-ajax-call-in-angular-js – jbe Jun 04 '16 at 11:56
  • Benefits of services: easy code reuse, easy controller and service testing, easy service mocking. – dfsq Jun 04 '16 at 12:03

4 Answers4

2

A controller carries out presentation logic (it acts as a viewmodel in Angular Model-View-Whatever pattern). Services do business logic (model). It is battle-proven separation of concerns and inherent part of OOP good practices.

Thin controllers and fat services guarantee that app units stay reusable, testable and maintainable.

There's no benefit in replacing $http with RestService if they are the same thing. The proper separation of business and presentation logic is expected to be something like this

$scope.getCurrentUser = function() {
  return UserService.getCurrent()
  .then(function(user) {
    $scope.currentUser = user.id;
  })
  .catch(function(err) {
    alert("Browser failed to get current user.");
    throw err;
  });
});

It takes care of result conditioning and returns a promise. getCurrentUser passes a promise, so it could be chained if needed (by other controller method or test).

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Nice reminder of the separation of concerns while I'm bogged down learning so much new stuff. I'm religious about it in regular C# apps. – ProfK Jun 04 '16 at 15:36
2

There are a few reasons why it is good practice in non-trivial applications.

Using a single generic service and passing in the url and parameters doesn't add so much value as you noticed. Instead you would have one method for each type of fetch that you need to do.

Some benefits of using services:

  • Re-usability. In a simple app, there might be one data fetch for each controller. But that can soon change. For example, you might have a product list page with getProducts, and a detail page with getProductDetail. But then you want to add a sale page, a category page, or show related products on the detail page. These might all use the original getProducts (with appropriate parameters).
  • Testing. You want to be able to test the controller, in isolation from an external data source. Baking the data fetch in to the controller doesn't make that easy. With a service, you just mock the service and you can test the controller with stable, known data.
  • Maintainability. You may decide that with simple services, it's a similar amount of code to just put it all in the controller, even if you're reusing it. What happens if the back-end path changes? Now you need to update it everywhere it's used. What happens if some extra logic is needed to process the data, or you need to get some supplementary data with another call? With a service, you make the change in one place. With it baked in to controllers, you have more work to do.
  • Code clarity. You want your methods to do clear, specific things. The controller is responsible for the logic around a specific part of the application. Adding in the mechanics of fetching data confuses that. With a simple example the only extra logic you need is to decode the json. That's not bad if your back-end returns exactly the data your controllers need in exactly the right format, but that may not be the case. By splitting the code out, each method can do one thing well. Let the service get data and pass it on to the controller in exactly the right format, then let the controller do it's thing.
James Waddington
  • 2,894
  • 2
  • 15
  • 24
0

It would make sense to have your service look like this:

app.factory('AccountService', function($http) {
    return {
        getCurrentUser: function(param1, param2) {
            var postOptions = {}; // build the postOptions based on params here
            return $http.post("/Account/CurrentUser", {}, postOptions)
            .then(function(response) {
              // do some common processing here
            });
        }
    };
});

Then calling this method would look this way:

$scope.getCurrentUser = function() {
    AccountService.getCurrentUser(param1, param2)
    .then(function(currentUser){
        // do your stuff here
    });
};

Which looks much nicer and lets you avoid the repetition of the backend service url and postOptions variable construction in multiple controllers.

Alexander Kravets
  • 4,245
  • 1
  • 16
  • 15
  • I must admit to being very naughty, and declaring `postOptions` as a module level variable, so I don't have to configure it in every controller. – ProfK Jun 04 '16 at 12:24
  • The other thing about doing it like this, is that your controller my not necessarily be the one calling the http endpoint. By doing abstracting the call to a service, you can then have other services call it and apply their own custom logic to the results - way before it gets to the controller to be displayed. – Callum Linington Jun 04 '16 at 12:51
0

Simple. Write every function as a service so that you can reuse it. As this is an asynchronous call use angular promise to send the data back to controller by wrapping it up within a promise.