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);
function AppCtrl($scope) {
$scope.name = "Bob";
}
<div ng-controller="AppCtrl">
Hello {{ name }}
</div>
[controller]
Add vm = this
at the top [controller]
Find/replace $scope
with vm
[view]
Add as someName
to any ng-controller
s [view]
Prepend someName.
to all variables
Here’s that example again, fixed up to use controllerAs
:
angular.controller('AppCtrl', AppCtrl);
function AppCtrl() {
var vm = this;
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);
function AppCtrl($scope, nameValidator) {
$scope.name = "Bob";
$scope.changeCount = 0;
$scope.$watch('name', function(newValue, oldValue) {
$scope.changeCount++;
});
}
<div ng-controller="AppCtrl as app">
Hello {{ name }}<br>
(changed {{ changeCount }} times).
</div>
(Only step 3 is new from before)
[controller]
Add vm = this
at the top [controller]
Find/replace $scope
with vm
[controller]
Prefix watched var
s with the controller name from the view. (app.
in this case) [view]
Add as someName
to any ng-controller
s [view]
Prepend someName.
to all variables
Here’s the controllerAs
version:
angular.controller('AppCtrl', AppCtrl);
function AppCtrl($scope, nameValidator) {
var vm = this;
vm.name = "Bob";
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.
CodeProject