diff options
| author | Misko Hevery | 2011-03-23 09:33:29 -0700 |
|---|---|---|
| committer | Vojta Jina | 2011-08-02 01:00:03 +0200 |
| commit | 8f0dcbab804180828d6859b1340c86cf161209fb (patch) | |
| tree | d13d47d47a1889cb7c96a87cecacd2e25307d51c /docs/content/guide/dev_guide.scopes.internals.ngdoc | |
| parent | 1f4b417184ce53af15474de065400f8a686430c5 (diff) | |
| download | angular.js-8f0dcbab804180828d6859b1340c86cf161209fb.tar.bz2 | |
feat(scope): new and improved scope implementation
- 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
Diffstat (limited to 'docs/content/guide/dev_guide.scopes.internals.ngdoc')
| -rw-r--r-- | docs/content/guide/dev_guide.scopes.internals.ngdoc | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/docs/content/guide/dev_guide.scopes.internals.ngdoc b/docs/content/guide/dev_guide.scopes.internals.ngdoc new file mode 100644 index 00000000..3aed7970 --- /dev/null +++ b/docs/content/guide/dev_guide.scopes.internals.ngdoc @@ -0,0 +1,196 @@ +@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} + |
