Front End Collision

Blog of software engineer Josh Beam

Get rid of $scope, and extend into the view model

23 August 2015 — Josh Beam — angularjs

All Posts
AngularJS's controllerAs syntax is a good first step into being able to have some sort of sense of hierarchy in applications. However, large controllers can still get unwieldy. We can move towards controlling our controllers with angular.extend.

TL;DR

Put all your controller properties into an object literal, and extend that object into this:

angular.extend(this, vm);

After that, always use this to refer to controller properties (don’t use vm.whatever).

A brief overview of controllerAs

Skip to the next section if you already know how to use this.

Basically, if you have a parent controller and a child controller nested within, you have to explicitly refer to $scope.$parent to access the parent controller from the child controller.

However, with controllerAs syntax, we get a namespace.

<div ng-controller="ParentCtrl as parent">
  
  {{parent.something}}
  
  <div ng-controller="ChildCtrl as child">
    
    {{child.something}}
    {{parent.something}}
    
  </div>
</div>

But then in your controller, you might have to deal with this:

// parent.controller.js
var vm = this;

vm.name = 'Bob';
vm.job = 'Builder';
vm.motto = 'Yes we can!';
vm.speak = speak;

function speak() {
  return vm.motto;
}

Also, wondering why we’re using vm? Check out John Papa’s AngularJS Style Guide.

Now imagine that, 1000x, when you have a controller full of lots and lots of stuff. Really, you could argue that you should consider leveraging directives and services for most of your business logic, but sometimes it’s difficult to do.

By the way, controllerAs still knows about $scope. This fake “namespace” simply happens internally by attaching an object to $scope. So in the above example, our parent controller $scope would look like this:

{
  // a bunch of $$ angular properties, and then...
  parent: {
    name: 'Bob',
    job: 'Builder',
    motto: 'Yes we can!',
    speak: function speak(){...}
  }
}

In fact, if you were to inject $scope into that controller and ask for $scope.parent, you’d see all those properties. There’s nothing fancy about it.

Extending the view model

Ever heard of angular.extend? It’s pretty nifty. It basically just puts properties from one object into another object, without overwriting any properties. AngularJS already attempts to protect us from this by delimiting internal properties with $ or $$, so it’s not really a concern anyway, but it’s a nice added touch.

So, we can just make our controller look like this:

// parent.controller.js
var vm = {
  name: 'Bob',
  job: 'Builder',
  motto: 'Yes we can!',
  speak: speak 
};

angular.extend(this, vm);

function speak() {
  return this.motto;
}

However, you’ll notice that it’ll usually be better from then on to continue to refer to controller properties with this rather than vm, because any changes through data-binding will propogate only to this (our namespaced controller), and not to our vm object reference. You can leverage things like Function.prototype.bind if you get into hairy contexts (like forEach loops, etc.).