scope is
an object that refers to the application model. It is an execution context
for expressions.
Scopes are arranged in hierarchical structure which mimic the DOM structure of
the application. Scopes can watch expressions and
propagate events.
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 "Angular realm" (controllers, services, Angular event handlers). -
Scopes can be nested to isolate application components while providing access to shared model properties. A scope (prototypically) inherits properties from its parent scope.
-
Scopes provide context against which expressions are evaluated. For example
{{username}}expression is meaningless, unless it is evaluated against a specific scope which defines theusernameproperty.
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 DOM. This is an important point since it makes the controllers view agnostic, which greatly improves the testing story of the applications.
Source
- <!doctype html>
- <htmlng-app>
- <head>
- <scriptsrc="http://code.angularjs.org/angular-1.0.2.min.js"></script>
- <scriptsrc="script.js"></script>
- </head>
- <body>
- <divng-controller="MyController">
- Your name:
- <inputtype="text"ng-model="username">
- <buttonng-click=‘sayHello()‘>greet</button>
- <hr>
- {{greeting}}
- </div>
- </body>
- </html>
Demo
Hello World!
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 greetingproperty. 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 intoMyController. (We will discuss scope hierarchies later.) -
Evaluate the
greetingexpression 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 testability, 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 =newMyController(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 Angular application has exactly one root
scope, but may have several child scopes.
The application can have multiple scopes, because some directives create new child scopes (refer to directive documentation to see which directives create new 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
When Angular evaluates {{username}}, it first looks at the
scope associated with the given element for
theusername 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.
This example illustrates scopes in application, and prototypical inheritance of properties.
Source
- <!doctype html>
- <htmlng-app>
- <head>
- <scriptsrc="http://code.angularjs.org/angular-1.0.2.min.js"></script>
- <scriptsrc="script.js"></script>
- </head>
- <body>
- <divng-controller="EmployeeController">
- Manager: {{employee.name}} [ {{department}} ]<br>
- Reports:
- <ul>
- <ling-repeat="employee in employee.reports">
- {{employee.name}} [ {{department}} ]
- </li>
- </ul>
- <hr>
- {{greeting}}
- </div>
- </body>
- </html>
Demo
Reports:
- John Smith [ Engineering ]
- Mary Run [ Engineering ]
Notice that the Angular automatically
places ng-scope class on elements where scopes are
attached. The <style>definition in this example
highlights in red the new scope locations. The child scopes are necessary
because the repeater
evaluates {{employee.name}} expression, but depending on
which scope the expression is evaluated it produces different result. Similarly
the evaluation of {{department}} prototypically inherits
from root scope, as it is the only place where
the department property is defined.
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 an
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 angular.
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
$0variable. -
To retrieve the associated scope in console execute:
angular.element($0).scope()
Scope Events Propagation
Scopes can propagate events in similar fashion to DOM events. The event can
be broadcasted to
the scope children or emitted to
scope parents.
Source
- <!doctype html>
- <htmlng-app>
- <head>
- <scriptsrc="http://code.angularjs.org/angular-1.0.2.min.js"></script>
- <scriptsrc="script.js"></script>
- </head>
- <body>
- <divng-controller="EventController">
- Root scope <tt>MyEvent</tt> count: {{count}}
- <ul>
- <ling-repeat="i in [1]"ng-controller="EventController">
- <buttonng-click="$emit(‘MyEvent‘)">$emit(‘MyEvent‘)</button>
- <buttonng-click="$broadcast(‘MyEvent‘)">$broadcast(‘MyEvent‘)</button>
- <br>
- Middle scope <tt>MyEvent</tt> count: {{count}}
- <ul>
- <ling-repeat="item in [1, 2]"ng-controller="EventController">
- Leaf scope <tt>MyEvent</tt> count: {{count}}
- </li>
- </ul>
- </li>
- </ul>
- </div>
- </body>
- </html>
Demo
-
Middle scope MyEvent count: 0- Leaf scope MyEvent count: 0
- Leaf scope MyEvent count: 0
Scope Life Cycle
The normal flow of 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 Angular
execution context, which means that Angular is unaware of model modifications.
To properly process model modifications the execution has to enter the Angular
execution context using the $apply method.
Only model modifications which execute inside the $applymethod
will be properly accounted for by Angular. 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 it guarantees that
during the $watchnotification no
other $watches are running. If
a $watch changes the value of the model, it will force
additional$digest cycle.
-
Creation
The
root scopeis created during the application bootstrap by the$injector. During template linking, some directives create new child scopes. -
Watcher registration
During template linking directives register
watcheson 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(). (Angular APIs do this implicitly, so no extra$applycall is needed when doing synchronous work in controllers, or asynchronous work with$httpor$timeoutservices. -
Mutation observation
At the end
$apply, Angular performs a$digestcycle on the root scope, which then propagates throughout all child scopes. During the$digestcycle, all$watched expressions or functions are checked for model mutation and if a mutation is detected, the$watchlistener 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$digestcalls into the child scope and allow for memory used by the child scope models to be reclaimed by the garbage collector.
Scopes and Directives
During the compilation phase, the compiler matches directives against
the DOM template. The directives usually fall into one of two categories:
-
Observing
directives, such as double-curly expressions{{expression}}, register listeners using the$watch()method. This type of directive needs to be notified whenever the expression changes so that it can update the view. -
Listener directives, such as
ng-click, register a listener with the DOM. When the DOM listener fires, the directive executes the associated expression and updates the view using the$apply()method.
When an external event (such as a user action, timer or XHR) is received, the
associated expression must
be applied to the scope through the $apply() method
so that all listeners are updated correctly.
Directives that Create Scopes
In most cases, directives and
scopes interact but do not create new instances of scope. However, some
directives, such as ng-controller and ng-repeat,
create new child scopes and attach the child scope to the corresponding DOM
element. You can retrieve a scope for any DOM element by using
anangular.element(aDomElement).scope() method call.
Controllers and Scopes
Scopes and controllers interact with each other in the following situations:
-
Controllers use scopes to expose controller methods to templates (see
ng-controller). -
Controllers define methods (behavior) that can mutate the model (properties on the scope).
-
Controllers may register
watcheson the model. These watches execute immediately after the controller behavior executes.
See the ng-controller for
more information.
Scope $watch Performance Considerations
Dirty checking the scope for property changes is a common operation in Angular and for this reason the dirty checking function must be efficient. Care should be taken that the dirty checking function does not do any DOM access, as DOM access is orders of magnitude slower then property access on JavaScript object.