Search code examples
javascriptangularjsangularjs-scopeangularjs-service

How should I reference services in my controller functions without using scope?


After reading several articles on avoiding scope soup and referencing the Google Guidelines for building controllers I have been left with one burning question. How should I reference my injected dependencies within my controller?

My approach thus far is to put the services on my object but I'm not exactly happy with that as now my services are exposed to the outside world (the template markup). What is the correct approach to build a controller without directly referencing $scope, and have my injected dependencies available to the controller but not exposed publicly.

As you can see below my work around is to put $http on 'this' and then reference it in my prototyped functions. Not my ideal choice for the aforementioned reasons.

http://plnkr.co/edit/Tn6Jkk06Zdu92uTM0UdU?p=preview

DOCTYPE html>
<html>

<head>
<script data-require="angular.js@*" data-semver="1.3.0-rc2" src="https://code.angularjs.org/1.3.0-rc.2/angular.js"></script>
<link rel="stylesheet" href="style.css" />

</head>

<body ng-controller="textController as txtCtrl">

<h1>{{txtCtrl.greeting}}</h1>
<button ng-click="txtCtrl.getData()">Get Names</button>
<hr>
{{txtCtrl.names | json}}
<script>

  var textController = function($http){
    this.greeting="Hello World";
    this.$http = $http;
  }

  textController.prototype.alert = function(){
    alert(this.greeting);
  }

  /*retrieve test data from //filltext.com*/
  textController.prototype.getData = function(){
    var that = this;

    this.$http({
      method: 'GET', 
      url: 'http://www.filltext.com/?rows=10&fname={firstName}&lname={lastName}'

    })
    .success(function(data){
      that.names=data;
    })
    .error();
  }

  angular.module("app",[])
  .controller("textController",textController);

  angular.bootstrap(document,["app"]);

</script>


Solution

  • One way you could do is to create a closure variable using IIFE and keep reference of http service outside the controller, and use it in the controller.

      (function(){
          var _$http; //<-- Here a private  variable
    
         ......
    
          /*retrieve test data from //filltext.com*/
          textController.prototype.getData = function(){
            var that = this;
    
           _$http({ //<-- Use it
              method: 'GET', 
              url: 'http://www.filltext.com/?rows=10&fname={firstName}&lname={lastName}' })...
             //...
          }
    
          angular.module("app").controller("textController",textController);
    
     })();
    

    Plnkr

    Or add a property to the constructor instead of its instance.

    (function(){
    
        var textController = function($http){
            this.greeting="Hello World";
            textController._$http = $http; //<-- Here set it
          }
    
          //....
    
          /*retrieve test data from //filltext.com*/
          textController.prototype.getData = function(){
            var that = this;
    
            textController._$http({ //<-- Use it
              method: 'GET', 
              url: 'http://www.filltext.com/?rows=10&fname={firstName}&lname={lastName}'})...
             //...
          }
         //...
         angular.module("app").controller("textController",textController);
    })();
    

    Plnkr

    Here is how you can explicitly annotate your dependencies (unless you use ng-annotate which will take of it while minification)

     angular.module("app").controller("textController",['$http', 'blah', textController]);
    

    Or

     textController.$inject = ['$http', 'blah'];
    

    However i would just leave the responsibility of making ajax calls and any data mapping to a service:-

    Example:-

      angular.module("app").service('UserService', ['$http', function($http){
           this.getUsers = function(searchObj){ //Get the input and set your url
            return $http({
              method: 'GET', 
              url: 'http://www.filltext.com/?rows=10',
              params: searchObj  //<-- Pass params
            });
    
           }
       }]);
    

    And just inject userService in the controller.

     var textController = function(userSvc){
        this.greeting="Hello World";
        this.userSvc = userSvc;
      }
    
    ....
    
      /*retrieve test data from //filltext.com*/
      textController.prototype.getData = function(){
        var that = this;
         //Call service with argument
        this.userSvc.getUsers({firstName:$scope.fn, lastName:$scope.ln}).success(function(data){
          that.names=data;
        })...;
      }
    
     ....
      angular.module("app").controller("textController",['UserService', textController]);
    

    Plnk3