What exactly is a scope in Angular? From the name, you might guess that it is a context for application state, possibly provided to protect us from using JavaScript's much-maligned global scope. That sounds like a simple, prudent thing for a framework to create, perhaps something we shouldn't even think about. Can we just move on to the next chapter?

Not so fast. Although this will not be a long chapter, it does cover something very important: scope inheritance and hierarchy. A typical Angular application might create a hierarchy of dozens, hundreds, or even thousands of scopes.

Before we get started, let's set up the environment for this chapter's examples.

Setup

In the Basics chapter, we learned how to add the ng-app directive to an element in order to tell Angular which part of your document it should process.

<body ng-app="app">
  <!-- Other examples to be inserted here. -->
</body>

The argument to ng-app is the name of our application's root module. (The example module name, app, is just a convention.) Angular modules will be covered in depth in an upcoming chapter. For now, just consider this some bootstrapping boilerplate that you can ignore.

angular.module('app', []);
angular.module('app').config(['$controllerProvider', function($controllerProvider) {
  $controllerProvider.allowGlobals();
}]);

Now that we have that out of the way, we can get on with business.

$scope

In the last chapter, Controllers, we learned how to prepare the model by attaching properties to a $scope reference. Let's repeat the exercise.

function NameController($scope) {
  $scope.name = "First";
}

Using the ng-controller directive, we can invoke the controller function above in the context of a DOM element. Any data that we assign to the scope provided to this controller will be available to us on and within this p element.

<p ng-controller="NameController"> {{name}} </p>

DOM elements that are outside of the element on which we have chosen to invoke NameController will not have access to the scope created for this usage of the controller. Let's test that this is true.

<div> <p> Outside the scope: {{name}} </p> <div ng-controller="NameController"> <p> Inside the scope: {{name}} </p> </div> </div>

Now that we have seen how scopes are paired with controllers, let's look at the opposite case: an Angular application with just one scope.

$rootScope

Angular is always trying its best to make sure we stay out of trouble when using scopes, so it creates a new one for every controller. However, scopes are hierarchical, and at the root of the scope hierarchy for every application is a single ancestor. We access this single root scope simply by declaring the specially-named $rootScope parameter in our controllers, and using it instead of the normal (and recommended!) $scope reference.

function RootNameController($rootScope) {
  $rootScope.name = "First";
}

This looks innocent enough, and indeed it works just fine.

<p ng-controller="RootNameController"> {{name}} </p>

However, trouble arises when we attempt to assign a property with the same name in another controller.

function SecondRootNameController($rootScope) {
  $rootScope.name = "Second";
}

This is not good. The $rootScope is a singleton in our application, and can only have one name property.

<p ng-controller="RootNameController"> {{name}} </p> <p ng-controller="SecondRootNameController"> {{name}} </p>

And indeed, our SecondRootNameController has replaced the value that RootNameController set on name. Well, that is the problem with globals, isn't it?

Isolation

By automatically giving each controller its own scope, Angular provides a significantly safer environment for us. Let's rewrite the controllers to publish the model the correct way, using $scope rather than $rootScope. Keep in mind that I only showed you the $rootScope examples above to illustrate why Angular creates a new scope object for each controller.

function SecondNameController($scope) {
  $scope.name = "Second";
}

Using the NameController that was shown at the beginning of this chapter, and the SecondNameController above, we can demonstrate the isolation of the scope objects that are passed to controllers declaring $scope.

<p ng-controller="NameController"> {{name}} </p> <p ng-controller="SecondNameController"> {{name}} </p>

This example produces the correct output, with a correct name value for each controller. This isolation is the behavior you get when neither controller is a child of the other, meaning neither is declared on a DOM element enclosed by that of the other controller. What will happen if we nest them instead?

Nesting

Modifying the last example slightly, we can move SecondNameController to a child element of the div that loads NameController, in order to produce a situation in which the controllers are nested.

<div ng-controller="NameController"> <p> {{name}} </p> <p ng-controller="SecondNameController"> {{name}} </p> </div>

Things still work correctly, in that the name properties are isolated from each other. What if you reverse the order of the p elements? Go ahead, give it a try. You should still see both name values in the output, just reversed.

It would seem that the models of nested controllers remain isolated, but this is misleading. In fact, Angular organizes controllers into a hierarchy based on their relative position in the DOM, and a nested controller inherits the properties of its ancestors. The reason we do not perceive a change is that the name property in the child scope shadows the property of the same name in the parent scope.

Let's see if we can coax out evidence of this behavior by changing the name of the property set on the child scope.

function ChildController($scope) {
  $scope.childName = "Child";
}

We'll render the two properties in both the parent and the child scopes.

<div ng-controller="NameController"> <p> {{name}} and {{childName}} </p> <p ng-controller="ChildController"> {{name}} and {{childName}} </p> </div>

This example makes it clear that NameController does not have access to the properties of its child, while ChildController has access to both its own properties and those of its parent.

Since name is an inherited property, it would make sense that we should see changes to it updated in both the parent and child scopes. Let's add an input that is bound to name on the parent.

<div ng-controller="NameController"> <p> {{name}} </p> <p ng-controller="ChildController"> {{name}} </p> <input type='text' ng-model='name'> </div>

If you try it editing name above, you'll see that this works as expected. The name property is updated in the context of both scopes. But again, please note that this is working with an input that is bound to name on the parent scope.

Inheritance

It would also make sense that we should be able to change name on the child scope and see the change reflected on the parent. At least, you would think so, right? Should we try? Let's add a text box that lets us modify the name property on the child scope.

In the rendered view of the example, carefully do the following: First, change the value in the upper text input. You will see the value of name updated everywhere. Second, change the value in the lower text input.

<div ng-controller="NameController"> <p> name: {{name}} <br> <input type='text' ng-model='name'> </p> <p ng-controller="ChildController"> name: {{name}} <br> <input type='text' ng-model='name'> </p> </div>

Are you surprised by the result?

Angular uses JavaScript's ordinary prototypal inheritance, which is partly good, because if you understand prototypal inheritance, you do not need to learn anything new. However, it is partly bad, because JavaScript prototypal inheritance is not entirely intuitive.

Setting a property on a JavaScript object results in the creation of that property on the object. This simple rule is bad news for inherited properties, which are shadowed by the object's own properties.

Huh? Put simply, there was no name property on the child until you modified the text in the lower input. Once you did that, Angular assigned the value to name in the child and the property was created. Once created, it blocked upward access to name on the parent.

Got it? If not, take a moment to study a bit more about prototypal inheritance. If so, let's continue.

How do we deal with this in Angular, so that we can modify model data on an inherited scope?

It's really easy. We just need to move the name property to another object.

function InfoController($scope) {
  $scope.info = {name: "First"};
}

function ChildInfoController($scope) {
  $scope.info.childName = "Child";
}

<div ng-controller="InfoController"> <p> {{info.name}} and {{info.childName}} <br> <input type='text' ng-model='info.name'> </p> <p ng-controller="ChildInfoController"> {{info.name}} and {{info.childName}} <br> <input type='text' ng-model='info.name'> </p> </div>

Notice that ChildInfoController depends on its parent to create the info object. What happens if you edit the source code for ChildInfoController, replacing the function body with this statement: $scope.info = {childName: "Second"};. Try it. You'll see that we are back to creating properties on the child, with the shadowing effect seen earlier.

scope.$watch

Most of the time, Angular's two-way binding interacts with properties on the scope as you would expect: When you use a bound input to make a change, the UI is updated everywhere. However, computed properties, meaning scope data derived from other scope data, are another story. In the example below, sum is a computed property.

function SumController($scope) {
  $scope.values = [1,2];
  $scope.newValue = 1;
  $scope.add = function() {
    $scope.values.push(parseInt($scope.newValue));
  };

  // Broken -- doesn't trigger UI update
  $scope.sum = $scope.values.reduce(function(a, b) {
    return a + b;
  });
}

The last statement in SumController is where sum is actually computed, in a simple operation using the reduce function.

In the template for this example, below, we use a select input to let the user choose a number (either 1, 2, or 3) to add to the end of the values array. (As an aside, notice that the controller provides an initial value for newValue. If it didn't, Angular would add a blank option to the select elements, in order to avoid arbitrarily setting newValue to the first option generated from the comprehension in ng-options. This behavior has nothing to do with scopes, but is useful to know.)

<p ng-controller="SumController"> <select ng-model="newValue" ng-options="n for n in [1,2,3]"></select> <input type="button" value="Add" ng-click="add()"> The sum of {{values}} is {{sum}}. </p>

Clicking Add should result in a change to the displayed value for sum, but sadly, it doesn't. Try it for yourself by selecting a value and clicking the Add button.

Let's fix things by moving the statement that computes the value of sum into a callback function. By passing this callback function as an argument to $scope.$watch along with a watchExpression argument (in this case, just the name of the property from which sum is computed), we arrange for sum to be recomputed whenever values is changed.

function SumController($scope) {
  $scope.values = [1,2];
  $scope.newValue = 1;
  $scope.add = function() {
    $scope.values.push(parseInt($scope.newValue));
  };
  $scope.$watch('values', function () {
    $scope.sum = $scope.values.reduce(function(a, b) {
      return a + b;
    });
  }, true);
}

And sure enough, the displayed value of sum is now dynamically updated in the view.

Example of two-way binding by passing two functions (getter and setter) to $watch

scope.$apply

Angular's built-in directives for two-way bindings are full-featured for sure, but every once in a while you will find some behavior that you need to add. For example, what if we want the user to be able to clear both the current state of a text input and its bound state on the scope with the esc key? How can we code this custom event handling?

<div ng-controller="EscapeController">
  <input type="text" ng-model="message">
  is bound to
  "<strong ng-bind="message"></strong>".
  Press <code>esc</code> to clear it!
</div>

First, we must declare the specially-named $element parameter on our controller, so that Angular injects a reference to the controller's associated DOM element. Using the bind function on the provided element, we register a callback for the keyup event in which we check for the esc key. In this callback we update the scope property. Easy enough, right? Give it a try. Type something, then press the esc key.

function EscapeController($scope, $element) {
  $scope.message = '';
  $element.bind('keyup', function (event) {
    if (event.keyCode === 27) { // esc key

      // Broken -- doesn't trigger UI update
      $scope.message = '';
    }
  });
}

Not quite. Since we're working (almost) directly with the DOM here, we need to let Angular know when it's time to repaint the view. We do this by wrapping our changes to the scope in a callback passed to $scope.$apply.

function EscapeController($scope, $element) {
  $scope.message = '';
  $element.bind('keyup', function (event) {
    if (event.keyCode === 27) { // esc key

      $scope.$apply(function() {
        $scope.message = '';
      });
    }
  });
}

Try it again in the rewritten example. Now that Angular knows what we're up to, things work perfectly.

Conclusion

If you're trying to fit Angular into the Model-view-controller (MVC) paradigm, scopes pose a bit of a conundrum. It starts out easy enough: Scopes are clearly part of the model layer. In Angular, an object isn't a model until it is reachable as a property of a scope. But the story gets more interesting when you look at how scopes are bound to the DOM via controllers or directives. Fortunately, our academic questions aside, scopes are intuitive and easy to use, as the examples in this chapter have shown.

I hope you have been enjoying Angular Basics.
You can join the mailing list to hear about updates to the book.
Please also let me know what you liked and what I can improve.
And please share the word using the social buttons below!