Prototypal Inheritance in AngularJS Scopes

Excerpt from AngularJS: Novice to Ninja

Prototypal Inheritance in AngularJS Scopes

The $rootScope object has a function called $new() that's used to create child scopes. Let's consider the previous example where we nested two controllers and had one $rootScope. The code is repeated below:

<div ng-app> <!-- creates a $rootScope -->
        <div ng-controller="OuterController"> <!--creates a scope 
āž„(call it scope 1) that inherits from $rootScope-->
            <div ng-controller="InnerController"> <!-- Creates a 
āž„child scope (call it scope 2) that inherits from scope 1
            </div>
        </div>
</div>

Here is how AngularJS handles scope hierarchies:

  1. AngularJS finds ng-app and therefore creates a $rootScope object.

  2. It encounters ng-controller and finds that it points to OuterController. So, it calls $rootScope.$new(), which creates a child scope(let's call it $scope1) that prototypally inherits from $rootScope. At this point the prototype (__proto__) of child scope $scope1 points to $rootScope. So anything attached to $rootScope is available in $child1. If the OuterController declares a dependency by adding a parameter $scope to the declaration of the constructor function, AngularJS calls it with the newly created scope $child1 as an argument.

  3. Next, while traversing the DOM, AngularJS encounters another ng-controller directive which points to InnerController. Now, it creates another child scope that prototypally inherits from $scope2. As before, calling $new() on $scope1 creates this child scope $scope2. The result of this inheritance is that $scope2 has access to all the properties defined by $scope1.

The figure in FigureĀ 3.1, taken from the AngularJS GitHub page, depicts how scopes inherit each other.

Inheritance in scopes

FigureĀ 3.1.Ā Inheritance in scopes

The inner scope always has access to the properties defined by the outer example. Apart from controllers, directives may also create child scopes. Some directives simply use the parent scope without creating a child. And some other directives create isolated scopes that don't inherit from any parent scope and exist on their own. We will discuss isolated scopes in the directives chapter.

To reinforce the above concepts let's create a sample app. We'll create an app that lists three books published by SitePoint. Using the Angular Seed project, follow these steps to create the app:

  1. Create the module myApp. This will go into app/js/app.js.

    'use strict';
    
    
    angular.module('myApp', [
      'myApp.controllers'
    ]);
    
    angular.module('myApp').run(function($rootScope){
        $rootScope.title='Famous Books';
        $rootScope.name="Root Scope";
    });
    
  2. Create the required controllers in app/js/controllers.js.

    angular.module('myApp.controllers',[]).controller('SiteController'
    āž„, function($scope){
      $scope.publisher='SitePoint';
      $scope.type="Web Development";
      $scope.name="Scope for SiteController";
    });
    
    angular.module('myApp.controllers').controller('BookController'
    āž„, function($scope){
      $scope.books = ['Jump Start HTML5','Jump Start CSS','Jump Start 
    āž„Responsive Web Design'];
      $scope.name="Scope for BookController";
    });
    
  3. Create the view. Name it scopes.html and this will be directly under app.

    <!DOCTYPE html>
    <html ng-app="myApp">
    <head>
      <meta charset="utf-8" />
      <title ng-bind="title"></title>
    </head>
    
    <body ng-controller="SiteController">
      <span>{{publisher}} excels in {{type}} books</span>
      <div ng-controller="BookController">
        <h3>Some of the popular books from {{publisher}}</h3>
        <ul>
          <li ng-repeat="book in books">
            {{book}}
          </li>
        </ul>
      </div>
    </body>
      <script src="lib/angular/angular.js"></script>
      <script src="js/app.js"></script>
      <script src="js/controllers.js"></script>
    </html>
    

In the above code, the callback passed to angular.module('myApp').run() gets called when all the modules are loaded. Inside this we are setting a model title on $rootScope. This is our page's title. The second one name is just used to identify the scope. We also have two controllers: SiteController and BookController and the latter is nested inside the former within the HTML.

Now let's visualize the scope hierarchy. Have you installed Angular Batarang yet? You'll need it to visualize the scope hierarchy, so if you've yet to install it, now's the time.

Now run the server by the command: node scripts/web-server.js. Once the server is up open the browser and go to http://localhost:8000/app/scopes.html. Bring up the Developer Tools and in the last tab you'll find AngularJS. Just enable it and refresh the page. Now you should see something like FigureĀ 3.2, which depicts the scope hierarchies!

A depiction of scope hierarchies

FigureĀ 3.2.Ā A depiction of scope hierarchies

As you can see Scope (002) is the root scope ($rootScope). Scope (003) is the scope for controller SiteController which prototypally inherits the $rootScope. Scope (004) is the scope for BookController which prototypally inherits from its parent Scope (003).

ng-repeat is a directive and, as the name suggests, it repeats an element multiple times. Here, for each book in model books the <li> is repeated and each <li> is linked with a different scope. Here, we have three such scopes named Scope (005), Scope (006), Scope (007). All these are siblings and prototypally inherit from Scope (004) Although the scopes are different the model name remains same i.e. book. The expression {{book}} in the HTML is repeated three times and, each time it's evaluated against three different scopes. That's why the result is different. So, an AngularJS scope is a context against which expressions are evaluated.

A model without an execution context is useless. Have a look at the view. We simply write model names in expressions like {{book}} and {{publisher}}. But AngularJS evaluates them against the correct context. When AngularJS sees {{publisher}} it evaluates this against the scope created for SiteController. You can also see that there's one more {{publisher}} expression in the markup under BookController scope. Still, we can see the value because this scope inherits from the SiteController scope and the models set on this scope are available in the child scopes.

Get instant access to all books and courses.

Free Trial