Controller prototype

Scope-based vs object-oriented controllers in Angular

It used to be that the AngularJS controllers shared state with the DOM (the view layer) via $scope object. The view could access any property and call any method on the $scope

1
2
3
<div ng-controller="MyController">
Hello {{ name }}! or {{ greeting() }}
</div>

The application code to initialize the controller (separate function for clarity)

1
2
3
4
5
6
7
8
function myController($scope) {
$scope.name = 'World';
$scope.greeting = function greeting() {
return 'Hello ' + $scope.name + '!';
};
}
angular.module('Example', [])
.controller('MyController', myController);

Each instance of the $scope object was created behind the scenes by the AngularJS. Each scope instance had its own copy of the property name (good) and its own copy of function greeting (bad, waste of space).

Fortunately, we can easily refactor this code to still keep separate data (the name property), but reuse single method greeting among all instances. We do this by binding a common function greeting to the $scope object inside the controller.

1
2
3
4
5
6
7
8
9
function greeting() {
return 'Hello ' + this.name + '!';
}
function myController($scope) {
$scope.name = 'World';
$scope.greeting = greeting.bind($scope);
}
angular.module('Example', [])
.controller('MyController', myController);

I prefer not using .bind in Angular, but in this case I don't mind it too much. Yet, there is a second problem with using $scope objects to tie the data from the model to the view. The $scope is too heavy! If you print a $scope to the console inside the controller it has lots of stuff

$$ChildScope: null
$$childHead: null
$$childTail: null
$$listenerCount: Object
$$listeners: Object
$$nextSibling: null
$$prevSibling: ChildScope
$$watchers: Array[2]
$$watchersCount: 2
$id: 4
$parent: Scope
greeting: ()
name: "Func"
__proto__: Scope

If we inspect its prototype object, we will find even more properties

$$ChildScope: ChildScope()
$$applyAsyncQueue: Array[0]
$$asyncQueue: Array[0]
$$childHead: ChildScope
$$childTail: ChildScope
$$destroyed: false
$$isolateBindings: null
$$listenerCount: Object
$$listeners: Object
$$nextSibling: null
$$phase: null
$$postDigestQueue: Array[0]
$$prevSibling: null
$$watchers: null
$$watchersCount: 6
$id: 1
$parent: null
$root: Scope
__proto__: Scope

Oh my! It makes no sense to expose all scope properties to the view (DOM). Why should we allow the view to print the number of watchers?

1
<div ng-controller="MyController">{{ $$watchersCount }}</div>

In AngularJS 1.3 a new syntax was introduced to separate controllers from scopes.

Now we can create a controller with a few properties that should be accessible from the DOM

1
2
3
<div ng-controller="MyController as ctrl" ng-init="ctrl.name = 'App'">
Hello {{ ctrl.name }}!
</div>

Major benefit: we must use ctrl.name in order to access the property, minimizing the clashes. We can set the properties that should be accessible from the DOM directly on the controller's this instance.

1
2
3
<div ng-controller="OOController as ctrl" ng-init="ctrl.name = 'App'">
Hello {{ ctrl.name }}!
</div>
1
2
3
4
5
6
7
8
function OOController() {
this.name = 'Object';
this.greeting = function greeting() {
return 'Hello ' + this.name + '!';
};
}
angular.module('Example', [])
.controller('OOController', OOController);

Even better, under the hood the AngularJS framework runs the controller function as a constructor for the new object.

1
2
var ctrl = Object.create({});
OOController.call(ctrl);

This gives us a perfect opportunity to move all common functions into a single place that is already familiar to anyone who programs JavaScript - the prototype object

1
2
3
4
5
6
7
8
function OOController() {
this.name = 'Object';
}
OOController.prototype.greeting = function greeting() {
return 'Hello ' + this.name + '!';
};
angular.module('Example', [])
.controller('OOController', OOController);

Notice how cool this code becomes - the OOController is plain JavaScript code that can be tested easily!

If you still need $scope, you can inject it into the controller function.

1
2
3
4
5
6
function OOController($scope) {
this.name = 'Object';
console.log($scope);
}
angular.module('Example', [])
.controller('OOController', OOController);

The $scope object will have lots of properties, linking it to the entire scope tree. It will also have $scope.ctrl property pointing at the controller object (which is now a separate object). I personally do not like this property, since the MVC model should not have access to the View or controller properties directly in my opinion.

We can react to the user events easily. For example we can add a button to change the name property on click

1
2
3
<div ng-controller="OOController as ctrl" ng-init="ctrl.name = 'App'">
Hello {{ ctrl.name }}! <button ng-click="ctrl.setName('clicked');">Click me</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
function OOController() {
this.name = 'Object';
}
OOController.prototype.greeting = function greeting() {
return 'Hello ' + this.name + '!';
};
OOController.prototype.setName = function setName(str) {
this.name = str;
};
angular.module('Example', [])
.controller('OOController', OOController);

Whenever the user clicks the button, the controller's name property is set, and the digest cycle runs.

I am still thinking how to better compose controllers like OOController with $scope objects (for emitting events on change for example, or watching $scope properties).