upgrade your angular II - controllers

In previous post we’ve seen how to take your angular services one step into the future.
Now, let’s talk about controllers.

Where is my $scope?

First thing you should know - they’ve killed the $scope. Well, not really killed it, you can still keep using it in angular 1.x, but it is best to use it as a service, and not as a state manager.
Assume that you have a controller, that uses a service to get more details from server:

angular.module('app')
    .controller('MyController', ['$scope', 'DetailsService', 
        function($scope, DetailsService){
            $scope.logDetails = function(){
                console.log(DetailsService.getDetails($scope.name));
            };
        }
    ]);

And HTML:

<div ng-controller="MyController">
    <input placeholder="name" ng-model="name"/>
    <button ng-click="logDetails()">Log Details</button>
</div>

controllerAs

Welcome to the real world.
Depending on the $scope hierarchy as the store of all data, is not a good idea. Instead, we’d like to use the javascript scope of the controller instance.
Controller instance? Yes. Your controller function is in fact a constructor that create controller instances.

angular.module('app')
    .controller('MyController', ['DetailsService', 
        function(DetailsService){
            this.logDetails = function(){
                console.log(DetailsService.getDetails(this.name));
            };
        }
    ]);

Now the state is on the this of the controller instance.
Another writing style is to use that:

angular.module('app')
    .controller('MyController', ['DetailsService', 
        function(DetailsService){
            var that = this;
            that.logDetails = function(){
                console.log(DetailsService.getDetails(that.name));
            };
        }
    ]);

Tip
I find it very easy to refactor the code, if we just add the var that = this; statement at the beginning of the controller, and then just replacing (ctrl+R) all $scope with that.

In the HTML we will use the controller as syntax:

<div ng-controller="MyController as ctrl">
     <input placeholder="name" ng-model="ctrl.name"/>
     <button ng-click="ctrl.logDetails()">Log Details</button>
</div>

For further reading, I recommend Todd Motto’s post.

Note
You can still inject and use the $scope for other purposes (i.e. $broadcast), but just don’t use it as a state manager.

class style

Now it is clear to use that the controller function is in fact a constructor function. So let’s refactor to class style:

(function () {
    function MyController(DetailsService) {
        this.logDetails = function () {
            console.log(DetailsService.getDetails(this.name));
        };
    }

    MyController.$inject = ['DetailsService'];

    angular.module('app').controller('MyController', MyController);

})();

Now, the definition of MyController is private withing that anonymous function. Note that angular.module(…) refers to the angular object on the global scope.

class with prototype

You may also write your class with prototype. It is considered best practice, but I find it a bit overhead…

(function () {
    function MyController(DetailsService) {
        this.name = null;
        this.DetailsService = DetailsService;
    }

    MyController.prototype.logDetails = function () {
       console.log(this.DetailsService.getDetails(this.name));
    };
    
    MyController.$inject = ['DetailsService'];

    angular.module('app').controller('MyController', MyController);

})();

Let’s wait for TypeScript…