Click here to Skip to main content
15,887,262 members
Articles / All Topics

Kill $scope - Replace it with controllerAs

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
9 Jan 2016CPOL2 min read 4.3K  
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:

JavaScript
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";
}
JavaScript
<div ng-controller="AppCtrl">
  Hello {{ name }}
</div>
Transform it!
  1. [controller] Add vm = this at the top
  2. [controller] Find/replace $scope with vm
  3. [view] Add as someName to any ng-controllers
  4. [view] Prepend someName. to all variables

Here’s that example again, fixed up to use controllerAs:

JavaScript
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";
}
JavaScript
<!-- 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:

JavaScript
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++;
  });
}
JavaScript
<div ng-controller="AppCtrl as app">
  Hello {{ name }}<br>
  (changed {{ changeCount }} times).
</div>
Transform it!

(Only step 3 is new from before)

  1. [controller] Add vm = this at the top
  2. [controller] Find/replace $scope with vm
  3. [controller] Prefix watched vars with the controller name from the view. (app. in this case)
  4. [view] Add as someName to any ng-controllers
  5. [view] Prepend someName. to all variables

Here’s the controllerAs version:

JavaScript
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++;
  });
}
JavaScript
<!-- 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:

Variable access using plain $scope

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:

Variable access using controllerAs

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.

This article was originally posted at https://daveceddia.com/feed.xml

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Dave is a Software Engineer in the Boston area and writes about AngularJS and other JavaScript things over at daveceddia.com

Comments and Discussions

 
-- There are no messages in this forum --