diff options
Diffstat (limited to 'docs/content/guide/scope.ngdoc')
| -rw-r--r-- | docs/content/guide/scope.ngdoc | 330 | 
1 files changed, 330 insertions, 0 deletions
| diff --git a/docs/content/guide/scope.ngdoc b/docs/content/guide/scope.ngdoc new file mode 100644 index 00000000..1946e7d8 --- /dev/null +++ b/docs/content/guide/scope.ngdoc @@ -0,0 +1,330 @@ +@ngdoc overview +@name Developer Guide: Scopes +@description + +# What are Scopes? + +{@link api/angular.module.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/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. + +  - 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/angular.module.ng.$compileProvider.directive directives} set up +{@link api/angular.module.ng.$rootScope.Scope#$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. + +<doc-example> +  <doc-source> +    <script> +      function MyController($scope) { +        $scope.username = 'World'; + +        $scope.sayHello = function() { +          $scope.greeting = 'Hello ' + $scope.username + '!'; +        }; +      } +    </script> +    <div ng-controller="MyController"> +      Your name: +        <input type="text" ng-model="username"> +        <button ng-click='sayHello()'>greet</button> +      <hr> +      {{greeting}} +    </div> +  </doc-source> +</doc-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 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. + +<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/angular.module.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 `{{username}}`, it first looks at the scope associated with the given +element for the `username` 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. + +<doc-example> +  <doc-source> +    <style> +      /* remove .doc-example-live in jsfiddle */ +      .doc-example-live .ng-scope { +        border: 1px dashed red; +      } +    </style> +    <script> +      function EmployeeController($scope) { +        $scope.department = 'Engineering'; +        $scope.employee = { +          name: 'Joe the Manager', +          reports: [ +            {name: 'John Smith'}, +            {name: 'Mary Run'} +          ] +        }; +      } +    </script> +    <div ng-controller="EmployeeController"> +      Manager: {{employee.name}} [ {{department}} ]<br> +      Reports: +        <ul> +          <li ng-repeat="employee in employee.reports"> +            {{employee.name}} [ {{department}} ] +          </li> +        </ul> +      <hr> +      {{greeting}} +    </div> +  </doc-source> +</doc-example> + +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 {@link api/angular.module.ng.$compileProvider.directive.ng:app `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: + +  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/angular.module.ng.$rootScope.Scope#$broadcast broadcasted} to the scope children or {@link +api/angular.module.ng.$rootScope.Scope#$emit emitted} to scope parents. + +<doc-example> +  <doc-source> +    <script> +      function EventController($scope) { +        $scope.count = 0; +        $scope.$on('MyEvent', function() { +          $scope.count++; +        }); +      } +    </script> +    <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> +  </doc-source> +</doc-example> + + + +## 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 they 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/angular.module.ng.$rootScope.Scope#$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/angular.module.ng.$compileProvider.directive.ng:click `ng-click`} it must evaluate the +expression inside the `$apply` method. + +After evaluating the expression `$apply` method performs a {@link +api/angular.module.ng.$rootScope.Scope#$digest `$digest`}. In $digest phase the scope examines all +of the `$watch` expressions and compares them with 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/angular.module.ng.$rootScope root scope} is created during the application +     bootstrap by the {@link api/angular.module.AUTO.$injector $injector}. During template +     linking, some directives create new child scopes. + +  2. **Watcher registration** + +     During template linking directives register {@link +     api/angular.module.ng.$rootScope.Scope#$watch watches} on the scope. This 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/angular.module.ng.$rootScope.Scope#$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/angular.module.ng.$http $http} or {@link +     api/angular.module.ng.$defer $defer} services. + +  4. **Mutation observation** + +     At the end `$apply`, Angular performs a {@link api/angular.module.ng.$rootScope.Scope#$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/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. + + +### Scopes and Directives + +During the compilation phase, the {@link compiler compiler} matches {@link +api/angular.module.ng.$compileProvider.directive directives} against the DOM template. The directives +usually fall into one of two categories: + +  - Observing {@link api/angular.module.ng.$compileProvider.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.module.ng.$compileProvider.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. + +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/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.module.ng.$compileProvider.directive directives} and scopes interact +but do not create new instances of scope. However, some directives, such as {@link +api/angular.module.ng.$compileProvider.directive.ng:controller ng:controller} and {@link +api/angular.module.ng.$compileProvider.directive.ng:repeat 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/angular.module.ng.$compileProvider.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. + +See the {@link controller controller documentation} 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. + | 
