mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 15:40:22 +00:00
225 lines
11 KiB
Text
225 lines
11 KiB
Text
@ngdoc overview
|
|
@name Developer Guide: Scopes: Scope Internals
|
|
@description
|
|
|
|
## What is a scope?
|
|
|
|
A scope is an execution context for {@link dev_guide.expressions expressions}. You can think of a
|
|
scope as a JavaScript object that has an extra set of APIs for registering change listeners and for
|
|
managing its own life cycle. In Angular's implementation of the model-view-controller design
|
|
pattern, a scope's properties comprise both the model and the controller methods.
|
|
|
|
|
|
### Scope characteristics
|
|
- Scopes provide APIs ({@link api/angular.module.ng.$rootScope.Scope#$watch $watch}) to observe model mutations.
|
|
- Scopes provide APIs ({@link api/angular.module.ng.$rootScope.Scope#$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.
|
|
- In some parts of the system (such as controllers, services and directives), the scope is made
|
|
available as `this` within the given context. (Note: This api will change before 1.0 is released.)
|
|
|
|
|
|
### Root scope
|
|
|
|
Every application has a root scope, which is the ancestor of all other scopes.
|
|
|
|
### What is scope used for?
|
|
|
|
{@link dev_guide.expressions Expressions} in the view are {@link api/angular.module.ng.$rootScope.Scope#$eval evaluated}
|
|
against the current scope. When HTML DOM elements are attached to a scope, expressions in those
|
|
elements are evaluated against the attached scope.
|
|
|
|
There are two kinds of expressions:
|
|
|
|
- Binding expressions, which are observations of property changes. Property changes are reflected
|
|
in the view during the {@link api/angular.module.ng.$rootScope.Scope#$digest digest cycle}.
|
|
- Action expressions, which are expressions with side effects. Typically, the side effects cause
|
|
execution of a method in a controller in response to a user action, such as clicking on a button.
|
|
|
|
|
|
### Scope inheritance
|
|
|
|
A scope (prototypically) inherits properties from its parent scope. Since a given property may not
|
|
reside on a child scope, if a property read does not find the property on a scope, the read will
|
|
recursively check the parent scope, grandparent scope, etc. all the way to the root scope before
|
|
defaulting to undefined.
|
|
|
|
{@link api/angular.directive Directives} associated with elements (ng:controller, ng:repeat,
|
|
ng:include, etc.) create new child scopes that inherit properties from the current parent scope.
|
|
Any code in Angular is free to create a new scope. Whether or not your code does so is an
|
|
implementation detail of the directive, that is, you can decide when or if this happens.
|
|
Inheritance typically mimics HTML DOM element nesting, but does not do so with the same
|
|
granularity.
|
|
|
|
A property write will always write to the current scope. This means that a write can hide a parent
|
|
property within the scope it writes to, as shown in the following example.
|
|
|
|
<pre>
|
|
var root = angular.module.ng.$rootScope.Scope();
|
|
var child = root.$new();
|
|
|
|
root.name = 'angular';
|
|
expect(child.name).toEqual('angular');
|
|
expect(root.name).toEqual('angular');
|
|
|
|
child.name = 'super-heroic framework';
|
|
expect(child.name).toEqual('super-heroic framework');
|
|
expect(root.name).toEqual('angular');
|
|
</pre>
|
|
|
|
|
|
### Scope life cycle
|
|
1. **Creation**
|
|
|
|
* The root scope is created by the {@link api/angular.module.ng.$rootScope $rootScope} service.
|
|
* To create a child scopes, you should call {@link api/angular.module.ng.$rootScope.Scope#$new parentScope.$new()}.
|
|
|
|
2. **Watcher registration**
|
|
|
|
Watcher registration can happen at any time and on any scope (root or child) via {@link
|
|
api/angular.module.ng.$rootScope.Scope#$watch scope.$watch()} API.
|
|
|
|
3. **Model mutation**
|
|
|
|
For mutations to be properly observed, you should make them only within the execution of the
|
|
function passed into {@link api/angular.module.ng.$rootScope.Scope#$apply scope.$apply()} call. (Angular apis do this
|
|
implicitly, so no extra `$apply` call is needed when doing synchronous work in controllers, or
|
|
asynchronous work with {@link api/angular.module.ng.$http $http} or {@link api/angular.module.ng.$defer
|
|
$defer} services.
|
|
|
|
4. **Mutation observation**
|
|
|
|
At the end of each `$apply` call {@link api/angular.module.ng.$rootScope.Scope#$digest $digest} cycle is started on
|
|
the root scope, which then propagates throughout all child scopes.
|
|
|
|
During the `$digest` cycle, all `$watch-ers` expressions or functions are checked for model
|
|
mutation and if a mutation is detected, the `$watch-er` 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/angular.module.ng.$rootScope.Scope#$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.
|
|
|
|
The root scope can't be destroyed via the `$destroy` API. Instead, it is enough to remove all
|
|
references from your application to the scope object and garbage collector will do its magic.
|
|
## Scopes in Angular applications
|
|
To understand how Angular applications work, you need to understand how scopes work within an
|
|
application. This section describes the typical life cycle of an application so you can see how
|
|
scopes come into play throughout and get a sense of their interactions.
|
|
### How scopes interact in applications
|
|
|
|
1. At application compile time, a root scope is created and is attached to the root `<HTML>` DOM
|
|
element.
|
|
2. During the compilation phase, the {@link dev_guide.compiler compiler} matches {@link
|
|
api/angular.directive directives} against the DOM template. The directives usually fall into one of
|
|
two categories:
|
|
- Observing {@link api/angular.directive directives}, such as double-curly expressions
|
|
`{{expression}}`, register listeners using the {@link api/angular.module.ng.$rootScope.Scope#$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/angular.directive.ng:click 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/angular.module.ng.$rootScope.Scope#$apply $apply()} method.
|
|
3. When an external event (such as a user action, timer or XHR) is received, the associated {@link
|
|
dev_guide.expressions expression} must be applied to the scope through the {@link
|
|
api/angular.module.ng.$rootScope.Scope#$apply $apply()} method so that all listeners are updated correctly.
|
|
|
|
|
|
### Directives that create scopes
|
|
In most cases, {@link api/angular.directive directives} and scopes interact but do not create new
|
|
instances of scope. However, some directives, such as {@link api/angular.directive.ng:controller
|
|
ng:controller} and {@link api/angular.widget.@ng:repeat ng:repeat}, create new child scopes using
|
|
the {@link api/angular.module.ng.$rootScope.Scope#$new $new()} method and then 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/angular.directive.ng:controller ng:controller}).
|
|
- Controllers define methods (behavior) that can mutate the model (properties on the scope).
|
|
- Controllers may register {@link api/angular.module.ng.$rootScope.Scope#$watch watches} on the model. These watches
|
|
execute immediately after the controller behavior executes, but before the DOM gets updated.
|
|
|
|
See the {@link dev_guide.mvc.understanding_controller controller docs} for more information.
|
|
|
|
### Updating scope properties
|
|
You can update a scope by calling its {@link api/angular.module.ng.$rootScope.Scope#$apply $apply()} method with an
|
|
expression or a function as the function argument. However it is typically not necessary to do this
|
|
explicitly. In most cases, angular intercepts all external events (such as user interactions, XHRs,
|
|
and timers) and wraps their callbacks into the `$apply()` method call on the scope object for you
|
|
at the right time. The only time you might need to call `$apply()` explicitly is when you create
|
|
your own custom asynchronous widget or service.
|
|
|
|
The reason it is unnecessary to call `$apply()` from within your controller functions when you use
|
|
built-in angular widgets and services is because your controllers are typically called from within
|
|
an `$apply()` call already.
|
|
|
|
When a user inputs data, angularized widgets invoke `$apply()` on the current scope and evaluate an
|
|
angular expression or execute a function on this scope. Afterwards `$apply` will trigger `$digest`
|
|
call on the root scope, to propagate your changes through the entire system, which results in
|
|
$watch-ers firing and view getting updated. Similarly, when a request to fetch data from a server
|
|
is made and the response comes back, the data is written into the model (scope) within an $apply,
|
|
which then pushes updates through to the view and any other dependents.
|
|
|
|
A widget that creates scopes (such as {@link api/angular.widget.@ng:repeat ng:repeat}) via `$new`,
|
|
doesn't need to worry about propagating the `$digest` call from the parent scope to child scopes.
|
|
This happens automatically.
|
|
|
|
## Scopes in unit-testing
|
|
You can create scopes, including the root scope, in tests using the {@link api/angular.module.ng.$rootScope.Scope
|
|
angular.module.ng.$rootScope.Scope()} API. This allows you to mimic the run-time environment and have full control over
|
|
the life cycle of the scope so that you can assert correct model transitions. Since these scopes
|
|
are created outside the normal compilation process, their life cycles must be managed by the test.
|
|
|
|
### Using scopes in unit-testing
|
|
The following example demonstrates how the scope life cycle needs to be manually triggered from
|
|
within the unit-tests.
|
|
|
|
<pre>
|
|
// example of a test
|
|
var scope = angular.module.ng.$rootScope.Scope();
|
|
scope.$watch('name', function(scope, name){
|
|
scope.greeting = 'Hello ' + name + '!';
|
|
});
|
|
|
|
scope.name = 'angular';
|
|
// The watch does not fire yet since we have to manually trigger the digest phase.
|
|
expect(scope.greeting).toEqual(undefined);
|
|
|
|
// manually trigger digest phase from the test
|
|
scope.$digest();
|
|
expect(scope.greeting).toEqual('Hello Angular!');
|
|
</pre>
|
|
|
|
|
|
### Dependency injection in Tests
|
|
|
|
When you find it necessary to inject your own mocks in your tests, use a scope to override the
|
|
service instances, as shown in the following example.
|
|
|
|
<pre>
|
|
it('should allow override of providers', inject(
|
|
function($provide) {
|
|
$provide.value('$location', {mode:'I am a mock'});
|
|
},
|
|
function($location){
|
|
expect($location.mode).toBe('I am a mock');
|
|
}
|
|
)};
|
|
</pre>
|
|
|
|
## Related Topics
|
|
|
|
* {@link dev_guide.scopes Angular Scope Objects}
|
|
* {@link dev_guide.scopes.understanding_scopes Understanding Scopes}
|
|
|
|
## Related API
|
|
|
|
* {@link api/angular.module.ng.$rootScope.Scope Angular Scope API}
|
|
|