In AngularJS, it is easy to throw together a simple application and have it work. However, as the application grows in size and complexity, the code can easily become a confusing and cumbersome jumble of logic. To help prevent this situation, the roles and responsibilities of the controllers and views and their interaction with the scope and model should be revisited and clarified.

MVC Basics

Let’s start with a few definitions provided by the Angular docs:

  • View: what the user sees (the DOM)
  • Controller: the business logic behind views
  • Model: the data that is shown to the user and with which the user interacts
  • Scope: context where the model is stored so that controllers, directives and expressions can access it
  • Service: reusable business logic independent of views

So far there is nothing that surprising. However, reviewing the definitions side by side with your code can often reveal misplaced logic. Is the View trying to do more than just present the model? Is the controller manipulating the DOM or performing other presentation logic? How are the view and controller interacting with the scope?

Who's in control?

According to the Angular docs on controllers and Miško Hevery’s best practices presentation, the controller’s job is to support the view by presenting it with the data and logic that is necessary for it to do its job. This boils down to two jobs:

  • Set up the initial state of the scope object.
  • Add behavior to the scope object.

You might notice that both of these items write to the scope. That brings up a rule of thumb that Miško mentions: the scope should generally be treated as write-only by the controller and read-only by the view. So this is all well and good in theory but how do we put this knowledge to work? Let's say you end up with a view that looks something like this:

<p ng-show='myObject.boolean && (myObject.word.length < myObject.number + 1)'>
  Conditionally show this text
</p>
Now, the view clearly shows that an element is conditionally visible and the controller is doing its job of supporting the view with business logic. As an added bonus, that logic is now also available to the controller's unit tests.

Model and Scope

The model is probably the most simple of the MVC trio but it is still worth revisiting. The controller may a few simple values on the scope and call it good. In this case, the model is not very well defined and that can lead to some unexpected results. Let’s say for example that the controller looks something like this:

angular.module('myApp', [])
.controller('myCtrl', ['$scope', function($scope) {
    $scope.firstName = 'Biff';
    $scope.lastName = 'Tannen';
}]);
Can you see the problem? The controller put a first and last name into scope but the ng-repeat creates a separate child scope with each repeat. Each child scope inherits from its parent scope in the standard Javascript prototypal inheritance sort of way. This means that simple properties like strings or numbers are inherited from the parent scopes until modified in a child scope. At that point, a copy of the property is made in the child, hiding the parent property's value. If the parent properties value is changed, that change is no longer reflected in the child scope. Try it out for yourself here. So what is the solution? Be deliberate in defining your data model by creating one or more model objects. Unlike simple property types, the values of properties on objects do not get copied to child scopes. All scopes will reference the same properties on the same object. In the above example, we could create an object called "person" with a first and last name. Then, the person object is our model. The controller can put the model in the scope and know that any changes to it in the view will be reflected in this singular object no matter how many child scopes are created. One quick test to make sure you are adhering to this best practice is to make sure that every ng-model has a "." in it.