153

I was reading this article: http://eviltrout.com/2013/06/15/ember-vs-angular.html

And it said,

Due to it’s lack of conventions, I wonder how many Angular projects rely on bad practices such as AJAX calls directly within controllers? Due to dependency injection, are developers injecting router parameters into directives? Are novice AngularJS developers going to structure their code in a way that an experienced AngularJS developer believes is idiomatic?

I am actually making $http calls from my Angular.js controller. Why is it a bad practice? What is the best practice for making $http calls then? and why?

Strawberry
  • 66,024
  • 56
  • 149
  • 197

4 Answers4

175

EDIT: This answer was primarily focus on version 1.0.X. To prevent confusion it's being changed to reflect the best answer for ALL current versions of Angular as of today, 2013-12-05.

The idea is to create a service that returns a promise to the returned data, then call that in your controller and handle the promise there to populate your $scope property.

The Service

module.factory('myService', function($http) {
   return {
        getFoos: function() {
             //return the promise directly.
             return $http.get('/foos')
                       .then(function(result) {
                            //resolve the promise as the data
                            return result.data;
                        });
        }
   }
});

The Controller:

Handle the promise's then() method and get the data out of it. Set the $scope property, and do whatever else you might need to do.

module.controller('MyCtrl', function($scope, myService) {
    myService.getFoos().then(function(foos) {
        $scope.foos = foos;
    });
});

In-View Promise Resolution (1.0.X only):

In Angular 1.0.X, the target of the original answer here, promises will get special treatment by the View. When they resolve, their resolved value will be bound to the view. This has been deprecated in 1.2.X

module.controller('MyCtrl', function($scope, myService) {
    // now you can just call it and stick it in a $scope property.
    // it will update the view when it resolves.
    $scope.foos = myService.getFoos();
});
Community
  • 1
  • 1
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • What is the difference between `.then` and `.success`? Does `.then` encompass both errors and success? – Strawberry Jul 15 '13 at 07:53
  • `.then` is a function of the actual returned promise. `.success` is a function added to the promise by `$http`. The primary difference is whatever is returned from the first function of `.then` will be resolved for the promise, it doesn't work that way with `.success`. – Ben Lesh Jul 15 '13 at 13:25
  • 4
    Just to mention, this only works when you use a the `$scope.foos` property in a template. If you were to use that same property outside of a template (for example in another function), the object stored there is still a promise object. – Clark Pan Jul 16 '13 at 04:26
  • +1 @ClarkPan, you're correct I guess I wasn't clear about that. – Ben Lesh Jul 16 '13 at 12:51
  • 1
    I am currently using this pattern in a new angular app, however I am wondering in a crud page how to get access to the property that I bound to the scope, in this example if I wanted to take the data from getFoos and post changes to it. if I try and access the $scope.foos in my update, I have the promise object and not the data, I can see how to get the data in the object itself, but it seems really really hacky.ideas? – Kelly Milligan Aug 15 '13 at 18:18
  • 5
    @KellyMilligan, in this pattern, it's the *binding* that knows what to do with the promise. If you need to access the object from anywhere else, you're going to have to handle the `.then()` of the promise and put the value in the $scope... `myService.getFoos().then(function(value) { $scope.foos = value; });` – Ben Lesh Aug 15 '13 at 20:14
  • Yep, that's what I ended up doing. – Kelly Milligan Aug 22 '13 at 16:04
  • 1
    Just an update on this technique, as of 1.2.0-rc.3, the auto un-wrapping of promises has been deprecated, so this technique will no longer work. – Clark Pan Oct 29 '13 at 22:50
  • 2
    Got a couple of downvotes here recently, presumably because it was no longer in-line with the latest version of Angular. I've updated the answer to reflect that. – Ben Lesh Dec 05 '13 at 18:28
  • This is a little confusing, you are assigning scope.foos to a service call that, when done, assigns scope.foos to actual data. – Jack Mar 14 '14 at 17:20
  • 1
    isnt $http a service itself ? why wrap it in another service ? – yossi Nov 27 '14 at 14:55
  • @yossi: Abstraction, reusability, encapsulation, organization. Any one or more of those. But you certainly don't *have* to. However, you probably *should* in many cases. – Ben Lesh Nov 30 '14 at 02:06
  • Took me a while to get this to work. Not sure if something has changed since this was posted but I had to use this syntax `module.controller('MyCtrl', ['$scope', 'myservice', function($scope, myService) { });` vs `module.controller('MyCtrl', function($scope, myService) {});` ... So this may need to be updated. – khollenbeck Jan 29 '15 at 22:05
  • Thanks for the answer! But wait, so for every controller that needs to make ajax calls you're supposed to also make a service that does the calls? That seems kind of confusing, and alot more work when having the ajax in the controller when it works fine. – Z2VvZ3Vp May 07 '15 at 18:26
  • 1
    @PixMach it's just abstraction. If you have a service that does your Ajax calls, it's liable to be easier to test, or at the very least, it will be more reusable. If you decide to use some other method to get the data besides Ajax, it's all abstracted away, so you can change your service without refactoring your Controller. You don't *have* to do this, of course. – Ben Lesh May 08 '15 at 17:33
  • Yes, this is the accepted best practice but often I think it is total overkill. I am working on a SPA where for a give Route (html + controller + test + css) the controller is really not any more complicated than above. In those cases, abstracting out the http call is just unneeded complexity. – honkskillet Sep 06 '15 at 22:55
45

The best practise would be to abstract the $http call out into a 'service' that provides data to your controller:

module.factory('WidgetData', function($http){
    return {
        get : function(params){
            return $http.get('url/to/widget/data', {
                params : params
            });
        }
    }
});

module.controller('WidgetController', function(WidgetData){
    WidgetData.get({
        id : '0'
    }).then(function(response){
        //Do what you will with the data.
    })
});

Abstracting the $http call like this will allow you to reuse this code across multiple controllers. This becomes necessary when the code that interacts with this data becomes more complex, perhaps you wish to process the data before using it in your controller, and cache the result of that process so you won't have to spend time re-processing it.

You should think of the 'service' as a representation (or Model) of data your application can use.

Clark Pan
  • 6,027
  • 1
  • 22
  • 18
9

The accepted answer was giving me the $http is not defined error so I had to do this:

var policyService = angular.module("PolicyService", []);
policyService.service('PolicyService', ['$http', function ($http) {
    return {
        foo: "bar",
        bar: function (params) {
            return $http.get('../Home/Policy_Read', {
                params: params
            });
        }
    };
}]);

The main difference being this line:

policyService.service('PolicyService', ['$http', function ($http) {
user1477388
  • 20,790
  • 32
  • 144
  • 264
1

I put an answer for someone who wanted a totally generic web service in Angular. I'd recommend just plugging it in and it will take care of all your web service calls without needing to code them all yourself. The answer is here:

https://stackoverflow.com/a/38958644/5349719

Community
  • 1
  • 1
cullimorer
  • 755
  • 1
  • 5
  • 23