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 | <div ng-controller="MyController"> |
The application code to initialize the controller (separate function for clarity)
1 | function myController($scope) { |
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 | function greeting() { |
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 | <div ng-controller="MyController as ctrl" ng-init="ctrl.name = 'App'"> |
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 | <div ng-controller="OOController as ctrl" ng-init="ctrl.name = 'App'"> |
1 | function OOController() { |
Even better, under the hood the AngularJS framework runs the controller function as a constructor for the new object.
1 | var ctrl = Object.create({}); |
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 | function 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 | function OOController($scope) { |
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 | <div ng-controller="OOController as ctrl" ng-init="ctrl.name = 'App'"> |
1 | function 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).