mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-16 23:30:23 +00:00
Before, there we multiple overview docs: - guide/overview - guide/introduction - guide/dev_guide.mvc - guide/dev_guide.mvc.understanding_model - guide/dev_guide.mvc.understanding_view - guide/concepts Now we have: - guide/introduction: High level description of Angular with the key benefits but without code or any concrete concepts - guide/concepts: explains all important concepts with a simple example and contains deep links to the other parts of the guide. All the old information was moved into existing documents or deleted when they were duplicates.
400 lines
19 KiB
Text
400 lines
19 KiB
Text
@ngdoc overview
|
|
@name Developer Guide: Scopes
|
|
@description
|
|
|
|
# What are Scopes?
|
|
|
|
{@link api/ng.$rootScope.Scope scope} is an object that refers to the application
|
|
model. It is an execution context for {@link expression expressions}. Scopes are
|
|
arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can
|
|
watch {@link guide/expression expressions} and propagate events.
|
|
|
|
## Scope characteristics
|
|
|
|
- Scopes provide APIs ({@link api/ng.$rootScope.Scope#methods_$watch $watch}) to observe
|
|
model mutations.
|
|
|
|
- Scopes provide APIs ({@link api/ng.$rootScope.Scope#methods_$apply $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 {@link guide/expression 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 {@link compiler
|
|
linking} phase the {@link api/ng.$compileProvider#methods_directive directives} set up
|
|
{@link api/ng.$rootScope.Scope#methods_$watch `$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.
|
|
|
|
<example>
|
|
<file name="script.js">
|
|
function MyController($scope) {
|
|
$scope.username = 'World';
|
|
|
|
$scope.sayHello = function() {
|
|
$scope.greeting = 'Hello ' + $scope.username + '!';
|
|
};
|
|
}
|
|
</file>
|
|
<file name="index.html">
|
|
<div ng-controller="MyController">
|
|
Your name:
|
|
<input type="text" ng-model="username">
|
|
<button ng-click='sayHello()'>greet</button>
|
|
<hr>
|
|
{{greeting}}
|
|
</div>
|
|
</file>
|
|
</example>
|
|
|
|
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` {@link guide/expression 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.
|
|
|
|
<pre>
|
|
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!');
|
|
});
|
|
</pre>
|
|
|
|
|
|
## Scope Hierarchies
|
|
|
|
Each Angular application has exactly one {@link api/ng.$rootScope root scope}, but
|
|
may have several child scopes.
|
|
|
|
The application can have multiple scopes, because some {@link guide/directive 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 `{{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.
|
|
|
|
This example illustrates scopes in application, and prototypical inheritance of properties. The example is followed by
|
|
a diagram depicting the scope boundaries.
|
|
|
|
<div class="show-scope">
|
|
<example>
|
|
<file name="index.html">
|
|
<div ng-controller="GreetCtrl">
|
|
Hello {{name}}!
|
|
</div>
|
|
<div ng-controller="ListCtrl">
|
|
<ol>
|
|
<li ng-repeat="name in names">{{name}}</li>
|
|
</ol>
|
|
</div>
|
|
</file>
|
|
<file name="script.js">
|
|
function GreetCtrl($scope) {
|
|
$scope.name = 'World';
|
|
}
|
|
|
|
function ListCtrl($scope) {
|
|
$scope.names = ['Igor', 'Misko', 'Vojta'];
|
|
}
|
|
</file>
|
|
<file name="style.css">
|
|
.show-scope .doc-example-live.ng-scope,
|
|
.show-scope .doc-example-live .ng-scope {
|
|
border: 1px solid red;
|
|
margin: 3px;
|
|
}
|
|
</file>
|
|
</example>
|
|
</div>
|
|
|
|
<img class="center" src="img/guide/concepts-scope.png">
|
|
|
|
Notice that 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 {@link api/ng.directive:ngApp `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 Angular.
|
|
|
|
To examine the scope in the debugger:
|
|
|
|
1. 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.
|
|
|
|
2. The debugger allows you to access the currently selected element in the console as `$0`
|
|
variable.
|
|
|
|
3. 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 {@link
|
|
api/ng.$rootScope.Scope#methods_$broadcast broadcasted} to the scope children or {@link
|
|
api/ng.$rootScope.Scope#methods_$emit emitted} to scope parents.
|
|
|
|
<example>
|
|
<file name="script.js">
|
|
function EventController($scope) {
|
|
$scope.count = 0;
|
|
$scope.$on('MyEvent', function() {
|
|
$scope.count++;
|
|
});
|
|
}
|
|
</file>
|
|
<file name="index.html">
|
|
<div ng-controller="EventController">
|
|
Root scope <tt>MyEvent</tt> count: {{count}}
|
|
<ul>
|
|
<li ng-repeat="i in [1]" ng-controller="EventController">
|
|
<button ng-click="$emit('MyEvent')">$emit('MyEvent')</button>
|
|
<button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button>
|
|
<br>
|
|
Middle scope <tt>MyEvent</tt> count: {{count}}
|
|
<ul>
|
|
<li ng-repeat="item in [1, 2]" ng-controller="EventController">
|
|
Leaf scope <tt>MyEvent</tt> count: {{count}}
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</file>
|
|
</example>
|
|
|
|
|
|
|
|
## 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 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 {@link
|
|
api/ng.$rootScope.Scope#methods_$apply `$apply`} method. Only model modifications which
|
|
execute inside the `$apply` method will be properly accounted for by Angular. For example if a
|
|
directive listens on DOM events, such as {@link
|
|
api/ng.directive:ngClick `ng-click`} it must evaluate the
|
|
expression inside the `$apply` method.
|
|
|
|
After evaluating the expression, the `$apply` method performs a {@link
|
|
api/ng.$rootScope.Scope#methods_$digest `$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 `$watch` notification no other
|
|
`$watch`es are running. If a `$watch` changes the value of the model, it will force additional
|
|
`$digest` cycle.
|
|
|
|
1. **Creation**
|
|
|
|
The {@link api/ng.$rootScope root scope} is created during the application
|
|
bootstrap by the {@link api/AUTO.$injector $injector}. During template
|
|
linking, some directives create new child scopes.
|
|
|
|
2. **Watcher registration**
|
|
|
|
During template linking directives register {@link
|
|
api/ng.$rootScope.Scope#methods_$watch watches} on the scope. These watches will be
|
|
used to propagate model values to the DOM.
|
|
|
|
3. **Model mutation**
|
|
|
|
For mutations to be properly observed, you should make them only within the {@link
|
|
api/ng.$rootScope.Scope#methods_$apply scope.$apply()}. (Angular APIs do this
|
|
implicitly, so no extra `$apply` call is needed when doing synchronous work in controllers,
|
|
or asynchronous work with {@link api/ng.$http $http} or {@link
|
|
api/ng.$timeout $timeout} services.
|
|
|
|
4. **Mutation observation**
|
|
|
|
At the end `$apply`, Angular performs a {@link api/ng.$rootScope.Scope#methods_$digest
|
|
$digest} cycle on the root scope, which then propagates throughout all child scopes. During
|
|
the `$digest` cycle, all `$watch`ed expressions or functions are checked for model mutation
|
|
and if a mutation is detected, the `$watch` listener is called.
|
|
|
|
5. **Scope destruction**
|
|
|
|
When child scopes are no longer needed, it is the responsibility of the child scope creator
|
|
to destroy them via {@link api/ng.$rootScope.Scope#methods_$destroy 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.
|
|
|
|
|
|
### Scopes and Directives
|
|
|
|
During the compilation phase, the {@link compiler compiler} matches {@link
|
|
api/ng.$compileProvider#methods_directive directives} against the DOM template. The directives
|
|
usually fall into one of two categories:
|
|
|
|
- Observing {@link api/ng.$compileProvider#methods_directive directives}, such as
|
|
double-curly expressions `{{expression}}`, register listeners using the {@link
|
|
api/ng.$rootScope.Scope#methods_$watch $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 {@link api/ng.directive:ngClick
|
|
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 {@link
|
|
api/ng.$rootScope.Scope#methods_$apply $apply()} method.
|
|
|
|
When an external event (such as a user action, timer or XHR) is received, the associated {@link
|
|
expression expression} must be applied to the scope through the {@link
|
|
api/ng.$rootScope.Scope#methods_$apply $apply()} method so that all listeners are updated
|
|
correctly.
|
|
|
|
### Directives that Create Scopes
|
|
|
|
In most cases, {@link api/ng.$compileProvider#methods_directive directives} and scopes interact
|
|
but do not create new instances of scope. However, some directives, such as {@link
|
|
api/ng.directive:ngController ng-controller} and {@link
|
|
api/ng.directive:ngRepeat 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 an `angular.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 {@link
|
|
api/ng.directive:ngController ng-controller}).
|
|
|
|
- Controllers define methods (behavior) that can mutate the model (properties on the scope).
|
|
|
|
- Controllers may register {@link api/ng.$rootScope.Scope#methods_$watch watches} on
|
|
the model. These watches execute immediately after the controller behavior executes.
|
|
|
|
See the {@link api/ng.directive:ngController 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.
|
|
|
|
## Integration with the browser event loop
|
|
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-runtime.png">
|
|
|
|
The diagram and the example below describe how Angular interacts with the browser's event loop.
|
|
|
|
1. 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).
|
|
2. The event's callback gets executed. This enters the JavaScript context. The callback can
|
|
modify the DOM structure.
|
|
3. Once the callback executes, the browser leaves the JavaScript context and
|
|
re-renders the view based on DOM changes.
|
|
|
|
Angular modifies the normal JavaScript flow by providing its own event processing loop. This
|
|
splits the JavaScript into classical and Angular execution context. Only operations which are
|
|
applied in Angular execution context will benefit from Angular data-binding, exception handling,
|
|
property watching, etc... You can also use $apply() to enter Angular 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.
|
|
|
|
1. Enter Angular execution context by calling {@link guide/scope scope}`.`{@link
|
|
api/ng.$rootScope.Scope#methods_$apply $apply}`(stimulusFn)`. Where `stimulusFn` is
|
|
the work you wish to do in Angular execution context.
|
|
2. Angular executes the `stimulusFn()`, which typically modifies application state.
|
|
3. Angular enters the {@link api/ng.$rootScope.Scope#methods_$digest $digest} loop. The
|
|
loop is made up of two smaller loops which process {@link
|
|
api/ng.$rootScope.Scope#methods_$evalAsync $evalAsync} queue and the {@link
|
|
api/ng.$rootScope.Scope#methods_$watch $watch} list. The {@link
|
|
api/ng.$rootScope.Scope#methods_$digest $digest} loop keeps iterating until the model
|
|
stabilizes, which means that the {@link api/ng.$rootScope.Scope#methods_$evalAsync
|
|
$evalAsync} queue is empty and the {@link api/ng.$rootScope.Scope#methods_$watch
|
|
$watch} list does not detect any changes.
|
|
4. The {@link api/ng.$rootScope.Scope#methods_$evalAsync $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.
|
|
5. The {@link api/ng.$rootScope.Scope#methods_$watch $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.
|
|
6. Once the Angular {@link api/ng.$rootScope.Scope#methods_$digest $digest} loop finishes
|
|
the execution leaves the Angular 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.
|
|
|
|
1. During the compilation phase:
|
|
1. the {@link api/ng.directive:ngModel ng-model} and {@link
|
|
api/ng.directive:input input} {@link guide/directive
|
|
directive} set up a `keydown` listener on the `<input>` control.
|
|
2. the {@link api/ng.$interpolate {{name}} } interpolation
|
|
sets up a {@link api/ng.$rootScope.Scope#methods_$watch $watch} to be notified of
|
|
`name` changes.
|
|
2. During the runtime phase:
|
|
1. Pressing an '`X`' key causes the browser to emit a `keydown` event on the input control.
|
|
2. The {@link api/ng.directive:input input} directive
|
|
captures the change to the input's value and calls {@link
|
|
api/ng.$rootScope.Scope#methods_$apply $apply}`("name = 'X';")` to update the
|
|
application model inside the Angular execution context.
|
|
3. Angular applies the `name = 'X';` to the model.
|
|
4. The {@link api/ng.$rootScope.Scope#methods_$digest $digest} loop begins
|
|
5. The {@link api/ng.$rootScope.Scope#methods_$watch $watch} list detects a change
|
|
on the `name` property and notifies the {@link api/ng.$interpolate
|
|
{{name}} } interpolation, which in turn updates the DOM.
|
|
6. Angular exits the execution context, which in turn exits the `keydown` event and with it
|
|
the JavaScript execution context.
|
|
7. The browser re-renders the view with update text.
|