diff options
Diffstat (limited to 'src/service/scope.js')
| -rw-r--r-- | src/service/scope.js | 1199 | 
1 files changed, 587 insertions, 612 deletions
diff --git a/src/service/scope.js b/src/service/scope.js index c4b9513b..94c41041 100644 --- a/src/service/scope.js +++ b/src/service/scope.js @@ -25,655 +25,630 @@   * are expensive to construct.   */ - -function createScope(providers, instanceCache) { -  var scope = new Scope(); -  (scope.$service = createInjector(scope, providers, instanceCache)).eager(); -  return scope; -} - - -/** - * @ngdoc function - * @name angular.scope - * - * @description - * A root scope can be created by calling {@link angular.scope angular.scope()}. Child scopes - * are created using the {@link angular.scope.$new $new()} method. - * (Most scopes are created automatically when compiled HTML template is executed.) - * - * Here is a simple scope snippet to show how you can interact with the scope. - * <pre> -       var scope = angular.scope(); -       scope.salutation = 'Hello'; -       scope.name = 'World'; - -       expect(scope.greeting).toEqual(undefined); - -       scope.$watch('name', function() { -         this.greeting = this.salutation + ' ' + this.name + '!'; -       }); // initialize the watch - -       expect(scope.greeting).toEqual(undefined); -       scope.name = 'Misko'; -       // still old value, since watches have not been called yet -       expect(scope.greeting).toEqual(undefined); - -       scope.$digest(); // fire all  the watches -       expect(scope.greeting).toEqual('Hello Misko!'); - * </pre> - * - * # Inheritance - * A scope can inherit from a parent scope, as in this example: - * <pre> -     var parent = angular.scope(); -     var child = parent.$new(); - -     parent.salutation = "Hello"; -     child.name = "World"; -     expect(child.salutation).toEqual('Hello'); - -     child.salutation = "Welcome"; -     expect(child.salutation).toEqual('Welcome'); -     expect(parent.salutation).toEqual('Hello'); - * </pre> - * - * # Dependency Injection - * See {@link guide/dev_guide.di dependency injection}. - * - * - * @param {Object.<string, function()>=} providers Map of service factory which need to be provided - *     for the current scope. Defaults to {@link angular.service}. - * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should - *     append/override services provided by `providers`. This is handy when unit-testing and having - *     the need to override a default service. - * @returns {Object} Newly created scope. - * - */ -function Scope() { -  this.$id = nextUid(); -  this.$$phase = this.$parent = this.$$watchers = -                 this.$$nextSibling = this.$$prevSibling = -                 this.$$childHead = this.$$childTail = null; -  this.$destructor = noop; -  this['this'] = this.$root =  this; -  this.$$asyncQueue = []; -  this.$$listeners = {}; -} - -/** - * @ngdoc property - * @name angular.scope.$id - * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for - *   debugging. - */ - -/** - * @ngdoc property - * @name angular.scope.$service - * @function - * - * @description - * Provides reference to an instance of {@link angular.injector injector} which can be used to - * retrieve {@link angular.service services}. In general the use of this api is discouraged, - * in favor of proper {@link guide/dev_guide.di dependency injection}. - * - * @returns {function} {@link angular.injector injector} - */ - -/** - * @ngdoc property - * @name angular.scope.$root - * @returns {Scope} The root scope of the current scope hierarchy. - */ - -/** - * @ngdoc property - * @name angular.scope.$parent - * @returns {Scope} The parent scope of the current scope. - */ - - -Scope.prototype = { +angularServiceInject('$rootScope', function($injector, $exceptionHandler){    /**     * @ngdoc function -   * @name angular.scope.$new -   * @function +   * @name angular.scope     *     * @description -   * Creates a new child {@link angular.scope scope}. The new scope can optionally behave as a -   * controller. The parent scope will propagate the {@link angular.scope.$digest $digest()} and -   * {@link angular.scope.$digest $digest()} events. The scope can be removed from the scope -   * hierarchy using {@link angular.scope.$destroy $destroy()}. -   * -   * {@link angular.scope.$destroy $destroy()} must be called on a scope when it is desired for -   * the scope and its child scopes to be permanently detached from the parent and thus stop -   * participating in model change detection and listener notification by invoking. -   * -   * @param {function()=} Class Constructor function which the scope should be applied to the scope. -   * @param {...*} curryArguments Any additional arguments which are curried into the constructor. -   *        See {@link guide/dev_guide.di dependency injection}. -   * @returns {Object} The newly created child scope. +   * A root scope can be created by calling {@link angular.scope angular.scope()}. Child scopes +   * are created using the {@link angular.scope.$new $new()} method. +   * (Most scopes are created automatically when compiled HTML template is executed.)     * -   */ -  $new: function(Class, curryArguments) { -    var Child = function() {}; // should be anonymous; This is so that when the minifier munges -      // the name it does not become random set of chars. These will then show up as class -      // name in the debugger. -    var child; -    Child.prototype = this; -    child = new Child(); -    child['this'] = child; -    child.$$listeners = {}; -    child.$parent = this; -    child.$id = nextUid(); -    child.$$asyncQueue = []; -    child.$$phase = child.$$watchers = -      child.$$nextSibling = child.$$childHead = child.$$childTail = null; -    child.$$prevSibling = this.$$childTail; -    if (this.$$childHead) { -      this.$$childTail.$$nextSibling = child; -      this.$$childTail = child; -    } else { -      this.$$childHead = this.$$childTail = child; -    } -    // short circuit if we have no class -    if (Class) { -      // can't use forEach, we need speed! -      var ClassPrototype = Class.prototype; -      for(var key in ClassPrototype) { -        child[key] = bind(child, ClassPrototype[key]); -      } -      this.$service.invoke(child, Class, curryArguments); -    } -    return child; -  }, +   * Here is a simple scope snippet to show how you can interact with the scope. +   * <pre> +         var scope = angular.scope(); +         scope.salutation = 'Hello'; +         scope.name = 'World'; -  /** -   * @ngdoc function -   * @name angular.scope.$watch -   * @function -   * -   * @description -   * Registers a `listener` callback to be executed whenever the `watchExpression` changes. -   * -   * - The `watchExpression` is called on every call to {@link angular.scope.$digest $digest()} and -   *   should return the value which will be watched. (Since {@link angular.scope.$digest $digest()} -   *   reruns when it detects changes the `watchExpression` can execute multiple times per -   *   {@link angular.scope.$digest $digest()} and should be idempotent.) -   * - The `listener` is called only when the value from the current `watchExpression` and the -   *   previous call to `watchExpression' are not equal. The inequality is determined according to -   *   {@link angular.equals} function. To save the value of the object for later comparison -   *   {@link angular.copy} function is used. It also means that watching complex options will -   *   have adverse memory and performance implications. -   * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This -   *   is achieved by rerunning the watchers until no changes are detected. The rerun iteration -   *   limit is 100 to prevent infinity loop deadlock. -   * -   * -   * If you want to be notified whenever {@link angular.scope.$digest $digest} is called, -   * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`, -   * can execute multiple times per {@link angular.scope.$digest $digest} cycle when a change is -   * detected, be prepared for multiple calls to your listener.) -   * -   * -   * # Example -     <pre> -       var scope = angular.scope(); -       scope.name = 'misko'; -       scope.counter = 0; - -       expect(scope.counter).toEqual(0); -       scope.$watch('name', function(scope, newValue, oldValue) { counter = counter + 1; }); -       expect(scope.counter).toEqual(0); - -       scope.$digest(); -       // no variable change -       expect(scope.counter).toEqual(0); - -       scope.name = 'adam'; -       scope.$digest(); -       expect(scope.counter).toEqual(1); -     </pre> +         expect(scope.greeting).toEqual(undefined); + +         scope.$watch('name', function() { +           this.greeting = this.salutation + ' ' + this.name + '!'; +         }); // initialize the watch + +         expect(scope.greeting).toEqual(undefined); +         scope.name = 'Misko'; +         // still old value, since watches have not been called yet +         expect(scope.greeting).toEqual(undefined); + +         scope.$digest(); // fire all  the watches +         expect(scope.greeting).toEqual('Hello Misko!'); +   * </pre>     * +   * # Inheritance +   * A scope can inherit from a parent scope, as in this example: +   * <pre> +       var parent = angular.scope(); +       var child = parent.$new(); + +       parent.salutation = "Hello"; +       child.name = "World"; +       expect(child.salutation).toEqual('Hello'); + +       child.salutation = "Welcome"; +       expect(child.salutation).toEqual('Welcome'); +       expect(parent.salutation).toEqual('Hello'); +   * </pre>     * +   * # Dependency Injection +   * See {@link guide/dev_guide.di dependency injection}.     * -   * @param {(function()|string)} watchExpression Expression that is evaluated on each -   *    {@link angular.scope.$digest $digest} cycle. A change in the return value triggers a -   *    call to the `listener`.     * -   *    - `string`: Evaluated as {@link guide/dev_guide.expressions expression} -   *    - `function(scope)`: called with current `scope` as a parameter. -   * @param {(function()|string)=} listener Callback called whenever the return value of -   *   the `watchExpression` changes. +   * @param {Object.<string, function()>=} providers Map of service factory which need to be provided +   *     for the current scope. Defaults to {@link angular.service}. +   * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should +   *     append/override services provided by `providers`. This is handy when unit-testing and having +   *     the need to override a default service. +   * @returns {Object} Newly created scope.     * -   *    - `string`: Evaluated as {@link guide/dev_guide.expressions expression} -   *    - `function(scope, newValue, oldValue)`: called with current `scope` an previous and -   *       current values as parameters. -   * @returns {function()} Returns a deregistration function for this listener.     */ -  $watch: function(watchExp, listener) { -    var scope = this, -        get = compileToFn(watchExp, 'watch'), -        listenFn = compileToFn(listener || noop, 'listener'), -        array = scope.$$watchers, -        watcher = { -          fn: listenFn, -          last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run. -          get: get, -          exp: watchExp -        }; - -    if (!array) { -      array = scope.$$watchers = []; -    } -    // we use unshift since we use a while loop in $digest for speed. -    // the while loop reads in reverse order. -    array.unshift(watcher); - -    return function() { -      angularArray.remove(array, watcher); -    }; -  }, +  function Scope() { +    this.$id = nextUid(); +    this.$$phase = this.$parent = this.$$watchers = +                   this.$$nextSibling = this.$$prevSibling = +                   this.$$childHead = this.$$childTail = null; +    this.$destructor = noop; +    this['this'] = this.$root =  this; +    this.$$asyncQueue = []; +    this.$$listeners = {}; +  }    /** -   * @ngdoc function -   * @name angular.scope.$digest -   * @function -   * -   * @description -   * Process all of the {@link angular.scope.$watch watchers} of the current scope and its children. -   * Because a {@link angular.scope.$watch watcher}'s listener can change the model, the -   * `$digest()` keeps calling the {@link angular.scope.$watch watchers} until no more listeners are -   * firing. This means that it is possible to get into an infinite loop. This function will throw -   * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 100. -   * -   * Usually you don't call `$digest()` directly in -   * {@link angular.directive.ng:controller controllers} or in {@link angular.directive directives}. -   * Instead a call to {@link angular.scope.$apply $apply()} (typically from within a -   * {@link angular.directive directive}) will force a `$digest()`. -   * -   * If you want to be notified whenever `$digest()` is called, -   * you can register a `watchExpression` function  with {@link angular.scope.$watch $watch()} -   * with no `listener`. -   * -   * You may have a need to call `$digest()` from within unit-tests, to simulate the scope -   * life-cycle. -   * -   * # Example -     <pre> -       var scope = angular.scope(); -       scope.name = 'misko'; -       scope.counter = 0; - -       expect(scope.counter).toEqual(0); -       scope.$digest('name', function(scope, newValue, oldValue) { counter = counter + 1; }); -       expect(scope.counter).toEqual(0); - -       scope.$digest(); -       // no variable change -       expect(scope.counter).toEqual(0); - -       scope.name = 'adam'; -       scope.$digest(); -       expect(scope.counter).toEqual(1); -     </pre> -   * +   * @ngdoc property +   * @name angular.scope.$id +   * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for +   *   debugging.     */ -  $digest: function() { -    var watch, value, last, -        watchers, -        asyncQueue, -        length, -        dirty, ttl = 100, -        next, current, target = this, -        watchLog = []; - -    if (target.$$phase) { -      throw Error(target.$$phase + ' already in progress'); -    } -    do { -      dirty = false; -      current = target; -      do { -        current.$$phase = '$digest'; -        asyncQueue = current.$$asyncQueue; -        while(asyncQueue.length) { -          try { -            current.$eval(asyncQueue.shift()); -          } catch (e) { -            current.$service('$exceptionHandler')(e); -          } + +  Scope.prototype = { +    /** +     * @ngdoc function +     * @name angular.scope.$new +     * @function +     * +     * @description +     * Creates a new child {@link angular.scope scope}. The new scope can optionally behave as a +     * controller. The parent scope will propagate the {@link angular.scope.$digest $digest()} and +     * {@link angular.scope.$digest $digest()} events. The scope can be removed from the scope +     * hierarchy using {@link angular.scope.$destroy $destroy()}. +     * +     * {@link angular.scope.$destroy $destroy()} must be called on a scope when it is desired for +     * the scope and its child scopes to be permanently detached from the parent and thus stop +     * participating in model change detection and listener notification by invoking. +     * +     * @param {function()=} Class Constructor function which the scope should be applied to the scope. +     * @param {...*} curryArguments Any additional arguments which are curried into the constructor. +     *        See {@link guide/dev_guide.di dependency injection}. +     * @returns {Object} The newly created child scope. +     * +     */ +    $new: function(Class, curryArguments) { +      var Child = function() {}; // should be anonymous; This is so that when the minifier munges +        // the name it does not become random set of chars. These will then show up as class +        // name in the debugger. +      var child; +      Child.prototype = this; +      child = new Child(); +      child['this'] = child; +      child.$$listeners = {}; +      child.$parent = this; +      child.$id = nextUid(); +      child.$$asyncQueue = []; +      child.$$phase = child.$$watchers = +        child.$$nextSibling = child.$$childHead = child.$$childTail = null; +      child.$$prevSibling = this.$$childTail; +      if (this.$$childHead) { +        this.$$childTail.$$nextSibling = child; +        this.$$childTail = child; +      } else { +        this.$$childHead = this.$$childTail = child; +      } +      // short circuit if we have no class +      if (Class) { +        // can't use forEach, we need speed! +        var ClassPrototype = Class.prototype; +        for(var key in ClassPrototype) { +          child[key] = bind(child, ClassPrototype[key]);          } -        if ((watchers = current.$$watchers)) { -          // process our watches -          length = watchers.length; -          while (length--) { +        $injector.invoke(child, Class, curryArguments); +      } +      return child; +    }, + +    /** +     * @ngdoc function +     * @name angular.scope.$watch +     * @function +     * +     * @description +     * Registers a `listener` callback to be executed whenever the `watchExpression` changes. +     * +     * - The `watchExpression` is called on every call to {@link angular.scope.$digest $digest()} and +     *   should return the value which will be watched. (Since {@link angular.scope.$digest $digest()} +     *   reruns when it detects changes the `watchExpression` can execute multiple times per +     *   {@link angular.scope.$digest $digest()} and should be idempotent.) +     * - The `listener` is called only when the value from the current `watchExpression` and the +     *   previous call to `watchExpression' are not equal. The inequality is determined according to +     *   {@link angular.equals} function. To save the value of the object for later comparison +     *   {@link angular.copy} function is used. It also means that watching complex options will +     *   have adverse memory and performance implications. +     * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This +     *   is achieved by rerunning the watchers until no changes are detected. The rerun iteration +     *   limit is 100 to prevent infinity loop deadlock. +     * +     * +     * If you want to be notified whenever {@link angular.scope.$digest $digest} is called, +     * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`, +     * can execute multiple times per {@link angular.scope.$digest $digest} cycle when a change is +     * detected, be prepared for multiple calls to your listener.) +     * +     * +     * # Example +       <pre> +         var scope = angular.scope(); +         scope.name = 'misko'; +         scope.counter = 0; + +         expect(scope.counter).toEqual(0); +         scope.$watch('name', function(scope, newValue, oldValue) { counter = counter + 1; }); +         expect(scope.counter).toEqual(0); + +         scope.$digest(); +         // no variable change +         expect(scope.counter).toEqual(0); + +         scope.name = 'adam'; +         scope.$digest(); +         expect(scope.counter).toEqual(1); +       </pre> +     * +     * +     * +     * @param {(function()|string)} watchExpression Expression that is evaluated on each +     *    {@link angular.scope.$digest $digest} cycle. A change in the return value triggers a +     *    call to the `listener`. +     * +     *    - `string`: Evaluated as {@link guide/dev_guide.expressions expression} +     *    - `function(scope)`: called with current `scope` as a parameter. +     * @param {(function()|string)=} listener Callback called whenever the return value of +     *   the `watchExpression` changes. +     * +     *    - `string`: Evaluated as {@link guide/dev_guide.expressions expression} +     *    - `function(scope, newValue, oldValue)`: called with current `scope` an previous and +     *       current values as parameters. +     * @returns {function()} Returns a deregistration function for this listener. +     */ +    $watch: function(watchExp, listener) { +      var scope = this, +          get = compileToFn(watchExp, 'watch'), +          listenFn = compileToFn(listener || noop, 'listener'), +          array = scope.$$watchers, +          watcher = { +            fn: listenFn, +            last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run. +            get: get, +            exp: watchExp +          }; + +      if (!array) { +        array = scope.$$watchers = []; +      } +      // we use unshift since we use a while loop in $digest for speed. +      // the while loop reads in reverse order. +      array.unshift(watcher); + +      return function() { +        angularArray.remove(array, watcher); +      }; +    }, + +    /** +     * @ngdoc function +     * @name angular.scope.$digest +     * @function +     * +     * @description +     * Process all of the {@link angular.scope.$watch watchers} of the current scope and its children. +     * Because a {@link angular.scope.$watch watcher}'s listener can change the model, the +     * `$digest()` keeps calling the {@link angular.scope.$watch watchers} until no more listeners are +     * firing. This means that it is possible to get into an infinite loop. This function will throw +     * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 100. +     * +     * Usually you don't call `$digest()` directly in +     * {@link angular.directive.ng:controller controllers} or in {@link angular.directive directives}. +     * Instead a call to {@link angular.scope.$apply $apply()} (typically from within a +     * {@link angular.directive directive}) will force a `$digest()`. +     * +     * If you want to be notified whenever `$digest()` is called, +     * you can register a `watchExpression` function  with {@link angular.scope.$watch $watch()} +     * with no `listener`. +     * +     * You may have a need to call `$digest()` from within unit-tests, to simulate the scope +     * life-cycle. +     * +     * # Example +       <pre> +         var scope = angular.scope(); +         scope.name = 'misko'; +         scope.counter = 0; + +         expect(scope.counter).toEqual(0); +         scope.$digest('name', function(scope, newValue, oldValue) { counter = counter + 1; }); +         expect(scope.counter).toEqual(0); + +         scope.$digest(); +         // no variable change +         expect(scope.counter).toEqual(0); + +         scope.name = 'adam'; +         scope.$digest(); +         expect(scope.counter).toEqual(1); +       </pre> +     * +     */ +    $digest: function() { +      var watch, value, last, +          watchers, +          asyncQueue, +          length, +          dirty, ttl = 100, +          next, current, target = this, +          watchLog = []; + +      if (target.$$phase) { +        throw Error(target.$$phase + ' already in progress'); +      } +      do { + +        dirty = false; +        current = target; +        do { +          current.$$phase = '$digest'; +          asyncQueue = current.$$asyncQueue; +          while(asyncQueue.length) {              try { -              watch = watchers[length]; -              // Most common watches are on primitives, in which case we can short -              // circuit it with === operator, only when === fails do we use .equals -              if ((value = watch.get(current)) !== (last = watch.last) && !equals(value, last)) { -                dirty = true; -                watch.last = copy(value); -                watch.fn(current, value, last); -                if (ttl < 5) { -                  if (!watchLog[4-ttl]) watchLog[4-ttl] = []; -                  if (isFunction(watch.exp)) { -                    watchLog[4-ttl].push('fn: ' + (watch.exp.name || watch.exp.toString())); -                  } else { -                    watchLog[4-ttl].push(watch.exp); +              current.$eval(asyncQueue.shift()); +            } catch (e) { +              current.$service('$exceptionHandler')(e); +            } +          } +          if ((watchers = current.$$watchers)) { +            // process our watches +            length = watchers.length; +            while (length--) { +              try { +                watch = watchers[length]; +                // Most common watches are on primitives, in which case we can short +                // circuit it with === operator, only when === fails do we use .equals +                if ((value = watch.get(current)) !== (last = watch.last) && !equals(value, last)) { +                  dirty = true; +                  watch.last = copy(value); +                  watch.fn(current, value, last); +                  if (ttl < 5) { +                    if (!watchLog[4-ttl]) watchLog[4-ttl] = []; +                    if (isFunction(watch.exp)) { +                      watchLog[4-ttl].push('fn: ' + (watch.exp.name || watch.exp.toString())); +                    } else { +                      watchLog[4-ttl].push(watch.exp); +                    }                    }                  } +              } catch (e) { +                $exceptionHandler(e);                } -            } catch (e) { -              current.$service('$exceptionHandler')(e);              }            } + +          current.$$phase = null; + +          // Insanity Warning: scope depth-first traversal +          // yes, this code is a bit crazy, but it works and we have tests to prove it! +          // this piece should be kept in sync with the traversal in $broadcast +          if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { +            while(current !== target && !(next = current.$$nextSibling)) { +              current = current.$parent; +            } +          } +        } while ((current = next)); + +        if(!(ttl--)) { +          throw Error('100 $digest() iterations reached. Aborting!\n' + +              'Watchers fired in the last 5 iterations: ' + toJson(watchLog));          } +      } while (dirty); +    }, + +    /** +     * @ngdoc function +     * @name angular.scope.$destroy +     * @function +     * +     * @description +     * Remove the current scope (and all of its children) from the parent scope. Removal implies +     * that calls to {@link angular.scope.$digest $digest()} will no longer propagate to the current +     * scope and its children. Removal also implies that the current scope is eligible for garbage +     * collection. +     * +     * The destructing scope emits an `$destroy` {@link angular.scope.$emit event}. +     * +     * The `$destroy()` is usually used by directives such as +     * {@link angular.widget.@ng:repeat ng:repeat} for managing the unrolling of the loop. +     * +     */ +    $destroy: function() { +      if (this.$root == this) return; // we can't remove the root node; +      this.$emit('$destroy'); +      var parent = this.$parent; + +      if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; +      if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; +      if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; +      if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; +    }, + +    /** +     * @ngdoc function +     * @name angular.scope.$eval +     * @function +     * +     * @description +     * Executes the `expression` on the current scope returning the result. Any exceptions in the +     * expression are propagated (uncaught). This is useful when evaluating engular expressions. +     * +     * # Example +       <pre> +         var scope = angular.scope(); +         scope.a = 1; +         scope.b = 2; + +         expect(scope.$eval('a+b')).toEqual(3); +         expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); +       </pre> +     * +     * @param {(string|function())=} expression An angular expression to be executed. +     * +     *    - `string`: execute using the rules as defined in  {@link guide/dev_guide.expressions expression}. +     *    - `function(scope)`: execute the function with the current `scope` parameter. +     * +     * @returns {*} The result of evaluating the expression. +     */ +    $eval: function(expr) { +      var fn = isString(expr) +        ? expressionCompile(expr) +        : expr || noop; +      return fn(this); +    }, + +    /** +     * @ngdoc function +     * @name angular.scope.$evalAsync +     * @function +     * +     * @description +     * Executes the expression on the current scope at a later point in time. +     * +     * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that: +     * +     *   - it will execute in the current script execution context (before any DOM rendering). +     *   - at least one {@link angular.scope.$digest $digest cycle} will be performed after +     *     `expression` execution. +     * +     * Any exceptions from the execution of the expression are forwarded to the +     * {@link angular.service.$exceptionHandler $exceptionHandler} service. +     * +     * @param {(string|function())=} expression An angular expression to be executed. +     * +     *    - `string`: execute using the rules as defined in  {@link guide/dev_guide.expressions expression}. +     *    - `function(scope)`: execute the function with the current `scope` parameter. +     * +     */ +    $evalAsync: function(expr) { +      this.$$asyncQueue.push(expr); +    }, + +    /** +     * @ngdoc function +     * @name angular.scope.$apply +     * @function +     * +     * @description +     * `$apply()` is used to execute an expression in angular from outside of the angular framework. +     * (For example from browser DOM events, setTimeout, XHR or third party libraries). +     * Because we are calling into the angular framework we need to perform proper scope life-cycle +     * of {@link angular.service.$exceptionHandler exception handling}, +     * {@link angular.scope.$digest executing watches}. +     * +     * ## Life cycle +     * +     * # Pseudo-Code of `$apply()` +        function $apply(expr) { +          try { +            return $eval(expr); +          } catch (e) { +            $exceptionHandler(e); +          } finally { +            $root.$digest(); +          } +        } +     * +     * +     * Scope's `$apply()` method transitions through the following stages: +     * +     * 1. The {@link guide/dev_guide.expressions expression} is executed using the +     *    {@link angular.scope.$eval $eval()} method. +     * 2. Any exceptions from the execution of the expression are forwarded to the +     *    {@link angular.service.$exceptionHandler $exceptionHandler} service. +     * 3. The {@link angular.scope.$watch watch} listeners are fired immediately after the expression +     *    was executed using the {@link angular.scope.$digest $digest()} method. +     * +     * +     * @param {(string|function())=} exp An angular expression to be executed. +     * +     *    - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. +     *    - `function(scope)`: execute the function with current `scope` parameter. +     * +     * @returns {*} The result of evaluating the expression. +     */ +    $apply: function(expr) { +      try { +        return this.$eval(expr); +      } catch (e) { +        $exceptionHandler(e); +      } finally { +        this.$root.$digest(); +      } +    }, + +    /** +     * @ngdoc function +     * @name angular.scope.$on +     * @function +     * +     * @description +     * Listen on events of a given type. See {@link angular.scope.$emit $emit} for discussion of +     * event life cycle. +     * +     * @param {string} name Event name to listen on. +     * @param {function(event)} listener Function to call when the event is emitted. +     * @returns {function()} Returns a deregistration function for this listener. +     * +     * The event listener function format is: `function(event)`. The `event` object passed into the +     * listener has the following attributes +     *   - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed. +     *   - `currentScope` - {Scope}: the current scope which is handling the event. +     *   - `name` - {string}: Name of the event. +     *   - `cancel` - {function=}: calling `cancel` function will cancel further event propagation +     *     (available only for events that were `$emit`-ed). +     */ +    $on: function(name, listener) { +      var namedListeners = this.$$listeners[name]; +      if (!namedListeners) { +        this.$$listeners[name] = namedListeners = []; +      } +      namedListeners.push(listener); + +      return function() { +        angularArray.remove(namedListeners, listener); +      }; +    }, + + +    /** +     * @ngdoc function +     * @name angular.scope.$emit +     * @function +     * +     * @description +     * Dispatches an event `name` upwards through the scope hierarchy notifying the +     * registered {@link angular.scope.$on} listeners. +     * +     * The event life cycle starts at the scope on which `$emit` was called. All +     * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified. +     * Afterwards, the event traverses upwards toward the root scope and calls all registered +     * listeners along the way. The event will stop propagating if one of the listeners cancels it. +     * +     * Any exception emmited from the {@link angular.scope.$on listeners} will be passed +     * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. +     * +     * @param {string} name Event name to emit. +     * @param {...*} args Optional set of arguments which will be passed onto the event listeners. +     */ +    $emit: function(name, args) { +      var empty = [], +          namedListeners, +          canceled = false, +          scope = this, +          event = { +            name: name, +            targetScope: scope, +            cancel: function() {canceled = true;} +          }, +          listenerArgs = concat([event], arguments, 1), +          i, length; -        current.$$phase = null; +      do { +        namedListeners = scope.$$listeners[name] || empty; +        event.currentScope = scope; +        for (i=0, length=namedListeners.length; i<length; i++) { +          try { +            namedListeners[i].apply(null, listenerArgs); +            if (canceled) return; +          } catch (e) { +            $exceptionHandler(e); +          } +        } +        //traverse upwards +        scope = scope.$parent; +      } while (scope); +    }, + + +    /** +     * @ngdoc function +     * @name angular.scope.$broadcast +     * @function +     * +     * @description +     * Dispatches an event `name` downwards to all child scopes (and their children) notifying the +     * registered {@link angular.scope.$on} listeners. +     * +     * The event life cycle starts at the scope on which `$broadcast` was called. All +     * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified. +     * Afterwards, the event propagates to all direct and indirect scopes of the current scope and +     * calls all registered listeners along the way. The event cannot be canceled. +     * +     * Any exception emmited from the {@link angular.scope.$on listeners} will be passed +     * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. +     * +     * @param {string} name Event name to emit. +     * @param {...*} args Optional set of arguments which will be passed onto the event listeners. +     */ +    $broadcast: function(name, args) { +      var target = this, +          current = target, +          next = target, +          event = { name: name, +                    targetScope: target }, +          listenerArgs = concat([event], arguments, 1); + +      //down while you can, then up and next sibling or up and next sibling until back at root +      do { +        current = next; +        event.currentScope = current; +        forEach(current.$$listeners[name], function(listener) { +          try { +            listener.apply(null, listenerArgs); +          } catch(e) { +            $exceptionHandler(e); +          } +        });          // Insanity Warning: scope depth-first traversal          // yes, this code is a bit crazy, but it works and we have tests to prove it! -        // this piece should be kept in sync with the traversal in $broadcast +        // this piece should be kept in sync with the traversal in $digest          if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {            while(current !== target && !(next = current.$$nextSibling)) {              current = current.$parent;            }          }        } while ((current = next)); - -      if(!(ttl--)) { -        throw Error('100 $digest() iterations reached. Aborting!\n' + -            'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); -      } -    } while (dirty); -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$destroy -   * @function -   * -   * @description -   * Remove the current scope (and all of its children) from the parent scope. Removal implies -   * that calls to {@link angular.scope.$digest $digest()} will no longer propagate to the current -   * scope and its children. Removal also implies that the current scope is eligible for garbage -   * collection. -   * -   * The destructing scope emits an `$destroy` {@link angular.scope.$emit event}. -   * -   * The `$destroy()` is usually used by directives such as -   * {@link angular.widget.@ng:repeat ng:repeat} for managing the unrolling of the loop. -   * -   */ -  $destroy: function() { -    if (this.$root == this) return; // we can't remove the root node; -    this.$emit('$destroy'); -    var parent = this.$parent; - -    if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; -    if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; -    if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; -    if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$eval -   * @function -   * -   * @description -   * Executes the `expression` on the current scope returning the result. Any exceptions in the -   * expression are propagated (uncaught). This is useful when evaluating engular expressions. -   * -   * # Example -     <pre> -       var scope = angular.scope(); -       scope.a = 1; -       scope.b = 2; - -       expect(scope.$eval('a+b')).toEqual(3); -       expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); -     </pre> -   * -   * @param {(string|function())=} expression An angular expression to be executed. -   * -   *    - `string`: execute using the rules as defined in  {@link guide/dev_guide.expressions expression}. -   *    - `function(scope)`: execute the function with the current `scope` parameter. -   * -   * @returns {*} The result of evaluating the expression. -   */ -  $eval: function(expr) { -    var fn = isString(expr) -      ? expressionCompile(expr) -      : expr || noop; -    return fn(this); -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$evalAsync -   * @function -   * -   * @description -   * Executes the expression on the current scope at a later point in time. -   * -   * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that: -   * -   *   - it will execute in the current script execution context (before any DOM rendering). -   *   - at least one {@link angular.scope.$digest $digest cycle} will be performed after -   *     `expression` execution. -   * -   * Any exceptions from the execution of the expression are forwarded to the -   * {@link angular.service.$exceptionHandler $exceptionHandler} service. -   * -   * @param {(string|function())=} expression An angular expression to be executed. -   * -   *    - `string`: execute using the rules as defined in  {@link guide/dev_guide.expressions expression}. -   *    - `function(scope)`: execute the function with the current `scope` parameter. -   * -   */ -  $evalAsync: function(expr) { -    this.$$asyncQueue.push(expr); -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$apply -   * @function -   * -   * @description -   * `$apply()` is used to execute an expression in angular from outside of the angular framework. -   * (For example from browser DOM events, setTimeout, XHR or third party libraries). -   * Because we are calling into the angular framework we need to perform proper scope life-cycle -   * of {@link angular.service.$exceptionHandler exception handling}, -   * {@link angular.scope.$digest executing watches}. -   * -   * ## Life cycle -   * -   * # Pseudo-Code of `$apply()` -      function $apply(expr) { -        try { -          return $eval(expr); -        } catch (e) { -          $exceptionHandler(e); -        } finally { -          $root.$digest(); -        } -      } -   * -   * -   * Scope's `$apply()` method transitions through the following stages: -   * -   * 1. The {@link guide/dev_guide.expressions expression} is executed using the -   *    {@link angular.scope.$eval $eval()} method. -   * 2. Any exceptions from the execution of the expression are forwarded to the -   *    {@link angular.service.$exceptionHandler $exceptionHandler} service. -   * 3. The {@link angular.scope.$watch watch} listeners are fired immediately after the expression -   *    was executed using the {@link angular.scope.$digest $digest()} method. -   * -   * -   * @param {(string|function())=} exp An angular expression to be executed. -   * -   *    - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. -   *    - `function(scope)`: execute the function with current `scope` parameter. -   * -   * @returns {*} The result of evaluating the expression. -   */ -  $apply: function(expr) { -    try { -      return this.$eval(expr); -    } catch (e) { -      this.$service('$exceptionHandler')(e); -    } finally { -      this.$root.$digest();      } -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$on -   * @function -   * -   * @description -   * Listen on events of a given type. See {@link angular.scope.$emit $emit} for discussion of -   * event life cycle. -   * -   * @param {string} name Event name to listen on. -   * @param {function(event)} listener Function to call when the event is emitted. -   * @returns {function()} Returns a deregistration function for this listener. -   * -   * The event listener function format is: `function(event)`. The `event` object passed into the -   * listener has the following attributes -   *   - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed. -   *   - `currentScope` - {Scope}: the current scope which is handling the event. -   *   - `name` - {string}: Name of the event. -   *   - `cancel` - {function=}: calling `cancel` function will cancel further event propagation -   *     (available only for events that were `$emit`-ed). -   */ -  $on: function(name, listener) { -    var namedListeners = this.$$listeners[name]; -    if (!namedListeners) { -      this.$$listeners[name] = namedListeners = []; -    } -    namedListeners.push(listener); - -    return function() { -      angularArray.remove(namedListeners, listener); -    }; -  }, - - -  /** -   * @ngdoc function -   * @name angular.scope.$emit -   * @function -   * -   * @description -   * Dispatches an event `name` upwards through the scope hierarchy notifying the -   * registered {@link angular.scope.$on} listeners. -   * -   * The event life cycle starts at the scope on which `$emit` was called. All -   * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified. -   * Afterwards, the event traverses upwards toward the root scope and calls all registered -   * listeners along the way. The event will stop propagating if one of the listeners cancels it. -   * -   * Any exception emmited from the {@link angular.scope.$on listeners} will be passed -   * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. -   * -   * @param {string} name Event name to emit. -   * @param {...*} args Optional set of arguments which will be passed onto the event listeners. -   */ -  $emit: function(name, args) { -    var empty = [], -        namedListeners, -        canceled = false, -        scope = this, -        event = { -          name: name, -          targetScope: scope, -          cancel: function() {canceled = true;} -        }, -        listenerArgs = concat([event], arguments, 1), -        i, length; - -    do { -      namedListeners = scope.$$listeners[name] || empty; -      event.currentScope = scope; -      for (i=0, length=namedListeners.length; i<length; i++) { -        try { -          namedListeners[i].apply(null, listenerArgs); -          if (canceled) return; -        } catch (e) { -          scope.$service('$exceptionHandler')(e); -        } -      } -      //traverse upwards -      scope = scope.$parent; -    } while (scope); -  }, +  }; +  // TODO(misko): remove this; +  var scope = new Scope(); +  scope.$service = $injector; +  return scope; -  /** -   * @ngdoc function -   * @name angular.scope.$broadcast -   * @function -   * -   * @description -   * Dispatches an event `name` downwards to all child scopes (and their children) notifying the -   * registered {@link angular.scope.$on} listeners. -   * -   * The event life cycle starts at the scope on which `$broadcast` was called. All -   * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified. -   * Afterwards, the event propagates to all direct and indirect scopes of the current scope and -   * calls all registered listeners along the way. The event cannot be canceled. -   * -   * Any exception emmited from the {@link angular.scope.$on listeners} will be passed -   * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. -   * -   * @param {string} name Event name to emit. -   * @param {...*} args Optional set of arguments which will be passed onto the event listeners. -   */ -  $broadcast: function(name, args) { -    var target = this, -        current = target, -        next = target, -        event = { name: name, -                  targetScope: target }, -        listenerArgs = concat([event], arguments, 1); - -    //down while you can, then up and next sibling or up and next sibling until back at root -    do { -      current = next; -      event.currentScope = current; -      forEach(current.$$listeners[name], function(listener) { -        try { -          listener.apply(null, listenerArgs); -        } catch(e) { -          current.$service('$exceptionHandler')(e); -        } -      }); - -      // Insanity Warning: scope depth-first traversal -      // yes, this code is a bit crazy, but it works and we have tests to prove it! -      // this piece should be kept in sync with the traversal in $digest -      if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { -        while(current !== target && !(next = current.$$nextSibling)) { -          current = current.$parent; -        } -      } -    } while ((current = next)); +  function compileToFn(exp, name) { +    var fn = isString(exp) +      ? expressionCompile(exp) +      : exp; +    assertArgFn(fn, name); +    return fn;    } -}; -function compileToFn(exp, name) { -  var fn = isString(exp) -    ? expressionCompile(exp) -    : exp; -  assertArgFn(fn, name); -  return fn; -} +}, ['$injector', '$exceptionHandler']);  | 
