Pippo's blog

it is all about software development

AngularJS: Controller as ... and closures

The ‘Controller as’ feature was introduced on AngularJS version 1.2. There is a good video from egghead.io on how to start using it.

This article also talks about this topic on the guide line #3. Take a few minutes and read that part of the article. Go ahead, I can wait.

Interesting, right? This feature helps a lot on your controller’s design and code maintainability, but let’s go one step further.

Let’s take the following controller and start to refactor it out:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 angular.module('app')
  .factory('MyResource', [ '$resource', function($resource) {
    return $resource('/dummyEndPoint');
  }])
  .controller('MainCtrl', [ '$scope', 'MyResource', function(scope, MyResource) {
    scope.save = function() {
      console.log(scope);
      var resource = {
        key1 : scope.key1,
        key2 : scope.key2
      };
      MyResource.save(resource, function() { scope.userSaved = true; });
    };
  }]);

… our html looks like this:

1
2
3
4
5
6
7
8
<body ng-app="app">
  <div ng-controller="MainCtrl">
    <input type="text" ng-model="key1" />
    <input type="text" ng-model="key2" />
    <button ng-click="save()"> Save </button>
    <i class="glyphicon glyphicon-ok" ng-show="userSaved"></i>
  </div>
</body>
  • Making the changes suggested by the article we end up with something like this:
1
2
3
4
5
6
7
8
<body ng-app="app">
  <div ng-controller="MainCtrl as main">
    <input type="text" ng-model="main.key1" />
    <input type="text" ng-model="main.key2" />
    <button ng-click="main.save()"> Save </button>
    <i class="glyphicon glyphicon-ok" ng-show="main.userSaved"></i>
  </div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var MainCtrl = function(MyResource) {
  this.save = function() {
    var resource = {
      key1 : this.key1,
      key2 : this.key2
    };
    MyResource.save(resource, function() { this.userSaved = true; });
  };
};

angular.module('app')
  .factory('MyResource', [ '$resource', function($resource) {
    return $resource('/dummyEndPoint');
  }])
  .controller('MainCtrl', [ 'MyResource', MainCtrl ]);

This looks better already, we are not coupled to the given $scope object anymore. Decoupling is always good. This javascript object is also easier to test and it is clear that we are using angular to do the dependency injection. Hmmm, but now instead of a $scope soup, we have a this soup. Still not clear what is exposed and as the controller grows, things get uglier pretty quick.

  • At this point 2 things come to my mind:
    1. Start using Javascript Closures, I will talk more about it in a second.
    2. Tell the save method which object to save, allowing angular itself to build the object for us with small DOM change.
1
2
3
4
5
6
7
8
9
10
11
var MainCtrl = function(MyResource) {
  var userSaved = false;
  var save = function(resource) {
    MyResource.save(resource, function() { userSaved = true; });
  };

  return {
     save : save,
     userSaved : userSaved
  };
};
1
2
3
4
5
6
  <div ng-controller="MainCtrl as main">
    <input type="text" ng-model="resource.key1" />
    <input type="text" ng-model="resource.key2" />
    <button ng-click="main.save(resource)"> Save </button>
    <i class="glyphicon glyphicon-ok" ng-show="main.userSaved"></i>
  </div>

How does it look to you so far?

You can go further and wrap MyResource in a function making the controller even smaller. Taking logic out of your controller into another functions that can be injected into other controllers is a very good way to allow for code reusability on the angular world.

In my opinion, closures is the best way to write good javascript. It mixes a little of OO and functional programming in a way your code keep a very good structure. You have private attributes and methods/functions and the best: you avoid the $scope/this soup.

In the same way you saw the $scope soup before, abusing the reserved word this is also evil. Extending objects with prototype also seems unnatural for me, use it just when there is no other option. Javascript Closures make very clear what we are exposing and do a very good job with encapsulation.

Let me know your thoughts!

Comments