Kill $scope - Replace it with controllerAs





0/5 (0 vote)
Kill $scope - Replace it with controllerAs
You’re chugging along great with your Angular app, following John Papa’s Style Guide like you’ve heard you should be. Then you run into a problem.
You found a solution on some blog but oh crap! It’s using $scope
all over the place! How can you pull it into your code without polluting your well-styled masterpiece?
Here, we’ll cover a couple quick things you can do to transform a $scope
’d mess into a sparkling paragon of virtuous code.
$scope becomes controllerAs
Start with a controller using $scope
:
angular.controller('AppCtrl', AppCtrl);
// 1: $scope is injected
function AppCtrl($scope) {
// 2: $scope is used to pass data to/from the view
$scope.name = "Bob";
}
<div ng-controller="AppCtrl">
Hello {{ name }}
</div>
Transform it!
[controller]
Addvm = this
at the top[controller]
Find/replace$scope
withvm
[view]
Addas someName
to anyng-controller
s[view]
PrependsomeName.
to all variables
Here’s that example again, fixed up to use controllerAs
:
angular.controller('AppCtrl', AppCtrl);
// 1: $scope is not injected
function AppCtrl() {
// 2: The controller itself is now exposed to the view
// Give it a name ('vm' or 'ctrl' is common)
var vm = this;
// 3: Find/Replace "$scope" with "vm"
vm.name = "Bob";
}
<!-- Add 'as app' to the controller -->
<div ng-controller="AppCtrl as app">
<!-- Prefix variables with 'app.' -->
Hello {{ app.name }}<br>
</div>
Notice that the view refers to the controller as “app
”, while the controller refers to itself as “vm
”. These names don’t effect each other.
Using $watch with controllerAs
What if you need to $watch
or $broadcast
from the controller? You can’t do it without $scope
!
This is ok though – think of $scope
as a service in this case. It’s giving you access to special behavior. You won’t use it to pass data to/from the view.
Here it is with $scope
:
angular.controller('AppCtrl', AppCtrl);
// 1: $scope is injected
function AppCtrl($scope, nameValidator) {
// 2: $scope passes data to view
$scope.name = "Bob";
// 3: $watch is setup on $scope variable
$scope.changeCount = 0;
$scope.$watch('name', function(newValue, oldValue) {
$scope.changeCount++;
});
}
<div ng-controller="AppCtrl as app">
Hello {{ name }}<br>
(changed {{ changeCount }} times).
</div>
Transform it!
(Only step 3 is new from before)
[controller]
Addvm = this
at the top[controller]
Find/replace$scope
withvm
[controller]
Prefix watchedvar
s with the controller name from the view. (app.
in this case)[view]
Addas someName
to anyng-controller
s[view]
PrependsomeName.
to all variables
Here’s the controllerAs
version:
angular.controller('AppCtrl', AppCtrl);
// 1: $scope is still injected (for $watch)
function AppCtrl($scope, nameValidator) {
var vm = this;
// 2: Use 'vm' instead.
vm.name = "Bob";
// 3: 'name' becomes 'app.name'
// (because in the view, this controller is called 'app')
vm.changeCount = 0;
$scope.$watch('app.name', function(newValue, oldValue) {
vm.changeCount++;
});
}
<!-- Add 'as app' to the controller -->
<div ng-controller="AppCtrl as app">
<!-- Prefix variables with 'app.' -->
Hello {{ app.name }}<br>
(changed {{ app.changeCount }} times).
</div>
Now, you know how to mechanically transform tutorials that use $scope
into cleaner code that uses controllerAs
!
So $scope
is vanquished now, right?
Well, not quite. $scope
never truly dies. Read on…
Behind the Scenes
Here’s what the $scope
hierarchy looks like normally:
When you refer to users
in your view, Angular looks on $scope
for it. If it’s not there, it will look to the prototypical parent, which is $rootScope
.
If there were any intermediate $parent
scopes, it would check those before checking $rootScope
.
It’s a plain old JavaScript prototypical inheritance tree: Check children first, then walk up the tree until the variable is found.
Here’s that same variable nested under a controllerAs-style
controller named ctrl
:
You write UserCtrl as ctrl
in your view and Angular inserts the controller itself onto $scope
, as $scope.ctrl
. What was previously just user
is now ctrl.user
.
This is why, when setting up a $watch
, “name
” became “app.name
” – everything is still a descendant of $scope
, but variables are now nested inside a named controller.
Clarity at Last
Hopefully, this cleared up some confusion around using tutorials out on the web.
As always, the best way to internalize this stuff is to practice.
Remember: There are no shortcuts, ONLY ZUUL! I mean practice. Only practice.
Kill $scope - Replace it with controllerAs was originally published by Dave Ceddia at Angularity on October 18, 2015.