AngularJS Scopes

The scope is the binding part between the HTML (view) and the JavaScript (controller). The scope is an object with the available properties and methods. The scope is available for both the view and the controller.

Unlike the other MVC frameworks, AngularJS doesn’t have specific classes or functions to create model objects. Instead, AngularJS extended the raw JavaScript objects with custom methods and properties. These objects, also known as scope in AngularJS terms, work as a glue between the view and other parts ( directives, controllers and services ) inside the AngularJS application.

Whenever the AngularJS application is bootstrapped, a rootScope object is created. Each scope created by controllers, directives and services are prototypically inherited from rootScope.

Using the Scope

When you make a controller in AngularJS, you pass the $scope object as an argument:

Example

Properties made in the controller, can be referred to in the view:

<div ng-app=”myApp” ng-controller=”myCtrl”>

<h1>{{carname}}</h1>

</div>

<script>

var app = angular.module(‘myApp’, []);

app.controller(‘myCtrl’, function($scope) {

$scope.carname = “Volvo”;

});

</script>

When adding properties to the $scope object in the controller, the view (HTML) gets access to these properties. In the view, you do not use the prefix $scope, you just refer to a propertyname, like {{carname}}.

If we consider an AngularJS application to consist of:

  • View, which is the HTML.
  • Model, which is the data available for the current view.
  • Controller, which is the JavaScript function that makes/changes/removes/controls the data.

Then the scope is the Model.

The scope is a JavaScript object with properties and methods, which are available for both the view and the controller.

Example

If you make changes in the view, the model and the controller will be updated:

<div ng-app=”myApp” ng-controller=”myCtrl”>

<input ng-model=”name”>

<h1>My name is {{name}}</h1>

</div>

<script>

var app = angular.module(‘myApp’, []);

app.controller(‘myCtrl’, function($scope) {

$scope.name = “Demo User”;

});

</script>

Scope Details

It is important to know which scope you are dealing with, at any time. In the two examples above there is only one scope, so knowing your scope is not an issue, but for larger applications there can be sections in the HTML DOM which can only access certain scopes.

Example

When dealing with the ng-repeat directive, each repetition has access to the current repetition object:

<div ng-app=”myApp” ng-controller=”myCtrl”>

<ul>

<li ng-repeat=”x in names”>{{x}}</li>

</ul>

</div>

<script>

var app = angular.module(‘myApp’, []);

app.controller(‘myCtrl’, function($scope) {

$scope.names = [“Emil”, “Tobias”, “Linus”];

});

</script>

Each <li> element has access to the current repetition object, in this case a string, which is referred to by using x.

Scope inside a directive

All directives have a scope associated with them. They use this scope for accessing data/methods inside the template and link function. By default, unless explicitly set, directives don’t create their own scope. Therefore, directives use their parent scope ( usually a controller ) as their own.

However, AngularJS allows us to change the default scope of directives by passing a configuration object known as directive definition object. A directive definition object –– let’s call it as DDO –– is a simple JavaScript object used for configuring the directive’s behaviour,template..etc

var app = angular.module(“test”,[]);

app.directive(“myDirective”,function(){

return {

restrict: “EA”,

scope: true,

link: function(scope,elem,attr){

// code goes here …

}

}

});

In the above example, we created a directive by returning a DDO from the function. There are a lot of properties of the DDO to learn, but here we’re just going to discuss the scope property, because, the values of scope property decides how the actual scope is created and used inside a directive. These values can be either “false”, “true” or “{}”. In the following sections, we’ll see how each of these affects directive’s behaviour.

Scope Characteristics

Scopes provide APIs ($watch) to observe model mutations. Scopes provide APIs ($apply) to propagate any model changes through the system into the view from outside of the “AngularJS realm” (controllers, services, AngularJS event handlers).

Scopes can be nested to limit access to the properties of application components while providing access to shared model properties. Nested scopes are either “child scopes” or “isolate scopes”. A “child scope” (prototypically) inherits properties from its parent scope. An “isolate scope” does not.

Scopes provide context against which expressions are evaluated. For example {{username}} expression is meaningless, unless it is evaluated against a specific scope which defines the username property.

Scope as Data-Model

Scope is the glue between application controller and the view. During the template linking phase the directives set up $watch expressions on the scope. The $watch allows the directives to be notified of property changes, which allows the directive to render the updated value to the DOM.

Both controllers and directives have reference to the scope, but not to each other. This arrangement isolates the controller from the directive as well as from the DOM. This is an important point since it makes the controllers view agnostic, which greatly improves the testing story of the applications.

index.html

<div ng-controller=”MyController”>

Your name:

<input type=”text” ng-model=”username”>

<button ng-click=’sayHello()’>greet</button>

<hr>

{{greeting}}

</div>

script.js

angular.module(‘scopeExample’, [])

.controller(‘MyController’, [‘$scope’, function($scope) {

$scope.username = ‘World’;

$scope.sayHello = function() {

$scope.greeting = ‘Hello ‘ + $scope.username + ‘!’;

};

}]);

In the above example notice that the MyController assigns World to the username property of the scope. The scope then notifies the input of the assignment, which then renders the input with username pre-filled. This demonstrates how a controller can write data into the scope.

Similarly the controller can assign behavior to scope as seen by the sayHello method, which is invoked when the user clicks on the ‘greet’ button. The sayHello method can read the username property and create a greeting property. This demonstrates that the properties on scope update automatically when they are bound to HTML input widgets.

Logically the rendering of {{greeting}} involves:

  • retrieval of the scope associated with DOM node where {{greeting}} is defined in template. In this example this is the same scope as the scope which was passed into MyController. (We will discuss scope hierarchies later.)
  • Evaluate the greeting expression against the scope retrieved above, and assign the result to the text of the enclosing DOM element.

You can think of the scope and its properties as the data which is used to render the view. The scope is the single source-of-truth for all things view related.

From a testability point of view, the separation of the controller and the view is desirable, because it allows us to test the behavior without being distracted by the rendering details.

it(‘should say hello’, function() {

var scopeMock = {};

var cntl = new MyController(scopeMock);

// Assert that username is pre-filled

expect(scopeMock.username).toEqual(‘World’);

// Assert that we read new username and greet

scopeMock.username = ‘angular’;

scopeMock.sayHello();

expect(scopeMock.greeting).toEqual(‘Hello angular!’);

});

Scope Hierarchies

Each AngularJS application has exactly one root scope, but may have any number of child scopes. The application can have multiple scopes, because directives can create new child scopes. When new scopes are created, they are added as children of their parent scope. This creates a tree structure which parallels the DOM where they’re attached.

The section Directives that Create Scopes has more info about which directives create scopes.

When AngularJS evaluates {{name}}, it first looks at the scope associated with the given element for the name property. If no such property is found, it searches the parent scope and so on until the root scope is reached. In JavaScript this behavior is known as prototypical inheritance, and child scopes prototypically inherit from their parents.

Retrieving Scopes from the DOM.

Scopes are attached to the DOM as $scope data property, and can be retrieved for debugging purposes. (It is unlikely that one would need to retrieve scopes in this way inside the application.) The location where the root scope is attached to the DOM is defined by the location of ng-app directive. Typically ng-app is placed on the <html> element, but it can be placed on other elements as well, if, for example, only a portion of the view needs to be controlled by AngularJS.

To examine the scope in the debugger:

  • Right click on the element of interest in your browser and select ‘inspect element’. You should see the browser debugger with the element you clicked on highlighted.
  • The debugger allows you to access the currently selected element in the console as $0 variable.
  • To retrieve the associated scope in console execute: angular.element($0).scope()

Scope Life Cycle

The normal flow of a browser receiving an event is that it executes a corresponding JavaScript callback. Once the callback completes the browser re-renders the DOM and returns to waiting for more events.

When the browser calls into JavaScript the code executes outside the AngularJS execution context, which means that AngularJS is unaware of model modifications. To properly process model modifications the execution has to enter the AngularJS execution context using the $apply method. Only model modifications which execute inside the $apply method will be properly accounted for by AngularJS. For example if a directive listens on DOM events, such as ng-click it must evaluate the expression inside the $apply method.

After evaluating the expression, the $apply method performs a $digest. In the $digest phase the scope examines all of the $watch expressions and compares them with the previous value. This dirty checking is done asynchronously. This means that assignment such as $scope.username=”angular” will not immediately cause a $watch to be notified, instead the $watch notification is delayed until the $digest phase. This delay is desirable, since it coalesces multiple model updates into one $watch notification as well as guarantees that during the $watch notification no other $watches are running. If a $watch changes the value of the model, it will force additional $digest cycle.

  • Creation – The root scope is created during the application bootstrap by the $injector. During template linking, some directives create new child scopes.
  • Watcher registration – During template linking, directives register watches on the scope. These watches will be used to propagate model values to the DOM.
  • Model mutation – For mutations to be properly observed, you should make them only within the scope.$apply(). AngularJS APIs do this implicitly, so no extra $apply call is needed when doing synchronous work in controllers, or asynchronous work with $http, $timeout or $interval services.
  • Mutation observation – At the end of $apply, AngularJS performs a $digest cycle on the root scope, which then propagates throughout all child scopes. During the $digest cycle, all $watched expressions or functions are checked for model mutation and if a mutation is detected, the $watch listener is called.
  • Scope destruction – When child scopes are no longer needed, it is the responsibility of the child scope creator to destroy them via scope.$destroy() API. This will stop propagation of $digest calls into the child scope and allow for memory used by the child scope models to be reclaimed by the garbage collector.

Integration with the browser event loop

The diagram and the example below describe how AngularJS interacts with the browser’s event loop.

  • The browser’s event-loop waits for an event to arrive. An event is a user interaction, timer event, or network event (response from a server).
  • The event’s callback gets executed. This enters the JavaScript context. The callback can modify the DOM structure.
  • Once the callback executes, the browser leaves the JavaScript context and re-renders the view based on DOM changes.

AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc… You can also use $apply() to enter the AngularJS execution context from JavaScript. Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks.

  • Enter the AngularJS execution context by calling scope.$apply(stimulusFn), where stimulusFn is the work you wish to do in the AngularJS execution context.
  • AngularJS executes the stimulusFn(), which typically modifies application state.
  • AngularJS enters the $digest loop. The loop is made up of two smaller loops which process $evalAsync queue and the $watch list. The $digest loop keeps iterating until the model stabilizes, which means that the $evalAsync queue is empty and the $watch list does not detect any changes.
  • The $evalAsync queue is used to schedule work which needs to occur outside of current stack frame, but before the browser’s view render. This is usually done with setTimeout(0), but the setTimeout(0) approach suffers from slowness and may cause view flickering since the browser renders the view after each event.
  • The $watch list is a set of expressions which may have changed since last iteration. If a change is detected then the $watch function is called which typically updates the DOM with the new value.
  • Once the AngularJS $digest loop finishes, the execution leaves the AngularJS and JavaScript context. This is followed by the browser re-rendering the DOM to reflect any changes.

Here is the explanation of how the Hello world example achieves the data-binding effect when the user enters text into the text field.

During the compilation phase:

  • the ng-model and input directive set up a keydown listener on the <input> control.
  • the interpolation sets up a $watch to be notified of name changes.

During the runtime phase:

  • Pressing an ‘X’ key causes the browser to emit a keydown event on the input control.
  • The input directive captures the change to the input’s value and calls $apply(“name = ‘X’;”) to update the application model inside the AngularJS execution context.
  • AngularJS applies the name = ‘X’; to the model.
  • The $digest loop begins
  • The $watch list detects a change on the name property and notifies the interpolation, which in turn updates the DOM.
  • AngularJS exits the execution context, which in turn exits the keydown event and with it the JavaScript execution context.
  • The browser re-renders the view with the updated text.

Root Scope

All applications have a $rootScope which is the scope created on the HTML element that contains the ng-app directive. The rootScope is available in the entire application.

If a variable has the same name in both the current scope and in the rootScope, the application uses the one in the current scope.

Example

A variable named “color” exists in both the controller’s scope and in the rootScope:

<body ng-app=”myApp”>

<p>The rootScope’s favorite color:</p>

<h1>{{color}}</h1>

<div ng-controller=”myCtrl”>

<p>The scope of the controller’s favorite color:</p>

<h1>{{color}}</h1>

</div>

<p>The rootScope’s favorite color is still:</p>

<h1>{{color}}</h1>

<script>

var app = angular.module(‘myApp’, []);

app.run(function($rootScope) {

$rootScope.color = ‘blue’;

});

app.controller(‘myCtrl’, function($scope) {

$scope.color = “red”;

});

</script>

</body>

Scope Methods

The $scope object contains various methods. The following table lists important methods of $scope object.

Method Description
$new() Creates new child scope.
$watch() Register a callback to be executed whenever model property changes.
$watchGroup() Register a callback to be executed whenever model properties changes. Here, specify an array of properties to be tracked.
$watchCollection() Register a callback to be executed whenever model object or array property changes.
$digest() Processes all of the watchers of the current scope and its children.
$destroy() Removes the current scope (and all of its children) from the parent scope.
$eval() Executes the expression on the current scope and returns the result.
$apply() Executes an expression in angular outside the angular framework.
$on() Register a callback for an event.
$emit() Dispatches the specified event upwards till $rootScope.
$broadcast() Dispatches the specified event downwards to all child scopes.

$watch

Angular scope object includes $watch event which will be raised whenever a model property is changed.

Example

<!DOCTYPE html>

<html>

<head>

<script src=”~/Scripts/angular.js”></script>

</head>

<body ng-app=”myNgApp”>

<div ng-controller=”myController”>

Enter Message: <input type=”text” ng-model=”message” /> <br />

New Message: {{newMessage}} <br />

Old Message: {{oldMessage}}

</div>

<script>

var ngApp = angular.module(‘myNgApp’, []);

ngApp.controller(‘myController’, function ($scope) {

$scope.message = “Hello World!”;

$scope.$watch(‘message’, function (newValue, oldValue) {

$scope.newMessage = newValue;

$scope.oldMessage = oldValue;

});

});

</script>

</body>

</html>

As you can see in the above example, $watch registers a callback, which will get called whenever the specified model property “message” changes.

Get industry recognized certification – Contact us

Menu