mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
- Speed improvements (about 4x on flush phase) - Memory improvements (uses no function closures) - Break $eval into $apply, $dispatch, $flush - Introduced $watch and $observe Breaks angular.equals() use === instead of == Breaks angular.scope() does not take parent as first argument Breaks scope.$watch() takes scope as first argument Breaks scope.$set(), scope.$get are removed Breaks scope.$config is removed Breaks $route.onChange callback has not "this" bounded
196 lines
9.6 KiB
Text
196 lines
9.6 KiB
Text
@workInProgress
|
|
@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 ($watch and $observe) 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.
|
|
- 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 functionality will change before 1.0 is
|
|
released.)
|
|
|
|
|
|
### Root scope
|
|
|
|
Every application has a root scope, which is the ancestor of all other scopes. The root scope is
|
|
responsible for creating the injector which is assigned to the {@link api/angular.scope.$service
|
|
$service} property, and initializing the services.
|
|
|
|
### What is scope used for?
|
|
|
|
{@link dev_guide.expressions Expressions} in the view are 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.scope.$flush flush 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.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>
|
|
|
|
|
|
|
|
## 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.
|
|
1. The root scope creates an {@link api/angular.injector injector} which is assigned to the
|
|
{@link api/angular.scope.$service $service} property of the root scope.
|
|
2. Any eager {@link api/angular.scope.$service services} are initialized at this point.
|
|
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.scope.$observe $observe()}
|
|
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.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.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.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.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.scope.$eval $eval()} method, but usually
|
|
you do not have to do this explicitly. In most cases, angular intercepts all external events (such
|
|
as user interactions, XHRs, and timers) and calls the `$eval()` method on the scope object for you
|
|
at the right time. The only time you might need to call `$eval()` explicitly is when you create
|
|
your own custom widget or service.
|
|
|
|
The reason it is unnecessary to call `$eval()` from within your controller functions when you use
|
|
built-in angular widgets and services is because a change in the data model triggers a call to the
|
|
`$eval()` method on the scope object where the data model changed.
|
|
|
|
When a user inputs data, angularized widgets copy the data to the appropriate scope and then call
|
|
the `$eval()` method on the root scope to update the view. It works this way because scopes are
|
|
inherited, and a child scope `$eval()` overrides its parent's `$eval()` method. Updating the whole
|
|
page requires a call to `$eval()` on the root scope as `$root.$eval()`. Similarly, when a request
|
|
to fetch data from a server is made and the response comes back, the data is written into the model
|
|
and then `$eval()` is called to push updates through to the view and any other dependents.
|
|
|
|
A widget that creates scopes (such as {@link api/angular.widget.@ng:repeat ng:repeat}) is
|
|
responsible for forwarding `$eval()` calls from the parent to those child scopes. That way, calling
|
|
`$eval()` on the root scope will update the whole page. This creates a spreadsheet-like behavior
|
|
for your app; the bound views update immediately as the user enters data.
|
|
|
|
## Scopes in unit-testing
|
|
You can create scopes, including the root scope, in tests using the {@link api/angular.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.
|
|
|
|
There is a key difference between the way scopes are called in Angular applications and in Angular
|
|
tests. In tests, the {@link api/angular.service.$updateView $updateView} calls the {@link
|
|
api/angular.scope.$flush $flush()} method synchronously.(This is in contrast to the asynchronous
|
|
calls used for applications.) Because test calls to scopes are synchronous, your tests are simpler
|
|
to write.
|
|
|
|
### 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.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>
|
|
var myLocation = {};
|
|
var scope = angular.scope(null, {$location: myLocation});
|
|
expect(scope.$service('$location')).toEqual(myLocation);
|
|
</pre>
|
|
|
|
## Related Topics
|
|
|
|
* {@link dev_guide.scopes Angular Scope Objects}
|
|
* {@link dev_guide.scopes.understanding_scopes Understanding Scopes}
|
|
|
|
## Related API
|
|
|
|
* {@link api/angular.scope Angular Scope API}
|
|
|