OdeToCode IC Logo

AngularJS Abstractions: Controllers

Monday, May 13, 2013

In MVC web programming server side controllers are responsible for reacting to an external stimulus (an HTTP request), and then building a model and possibly rendering a view in response to the stimulus. In a client app with AngularJS, the controller has an easier job.

AngularJS is officially a Model-View-Whatever framework, meaning there is quite a bit of flexibility in the architecture of an application. But, if you follow the typical conventions, a controller is simply a function the framework will call at the appropriate time. It’s the controller’s responsibility to put together a model by any means possible, and then the controller function is complete. 

One notable difference between a controller in server side world of Rails or ASP.NET MVC  and a controller with AngularJS is the view selection. Controllers and views are generally bound together on the client using directives (ng-controller) or in routing rules (a topic for a future post).

As an example, the following html is setting up the AboutController to manage the primary div element in the document. The primary div also contains some data binding expressions.

<body data-ng-app="patientApp">
                          
    <div data-ng-controller="AboutController">
        <h3>{{message}}</h3>
        <div>Number of rabbits in the yard: {{rabbitCount}}</div>

        <button ng-click="increase()">More rabbits</button>
        <button ng-click="decrease()">Less rabbits</button>
    </div>

    <script src="libs/angular.js"></script>
    <script src="scripts/myScript.js"></script>
</body>

When AngularJS takes control of the DOM it will see the ng-controller directive and go off searching for an AboutController. Here is one way to write the controller:

(function () {
    
    var AboutController = function($scope) {

        $scope.message = "Hello from the AboutController!";
        
        $scope.rabbitCount = 2;
        
        $scope.increase = function() {
            $scope.rabbitCount *= $scope.rabbitCount;
        };
        
        $scope.decrease = function() {
            $scope.rabbitCount -= 1;
        };
    };
    
    // Describe dependencies for the injector
    // in a minifier friendly way.
    AboutController.$inject = ["$scope"];

    // Register the controller as part of a module.
    // The patientApp module will need to take a 
    // dependency on patientApp.Controllers.
    angular.module("patientApp.Controllers")
           .controller("AboutController", AboutController);
    
}());

Once AngularJS finds the controller, the framework invokes the function and injects any dependencies the controller requires. In this case the controller is simple and only requires a $scope dependency. We’ll talk about $scope in a future post, for now you can think of $scope as the model object you need to enhance for the application to work. The HTML contains data binding expressions like {{message}} and ng-click directives that will send the framework looking for functions to invoke named increase and decrease. These are all attributes the controller needs to provide on the $scope object.

In many respects the $scope object feels like a view model in an MVVM environment like Silverlight. The data binding expressions push and pull data into properties of the view model. The command type bindings, like ng-click=increase(), invoke methods on the view model. And the view model (a.k.a $scope object) behaves in true view model like fashion in the sense that it knows nothing about the view or the DOM. The view model only needs to change values internally and data binding takes care of the rest. Clean and testable.