diff options
| author | Misko Hevery | 2011-08-10 13:15:43 -0700 | 
|---|---|---|
| committer | Misko Hevery | 2011-08-12 15:47:47 -0700 | 
| commit | 42062dab34192d2cb9ed66a720c0f791408c61c0 (patch) | |
| tree | ca85b56f12dd0138dbe3d7f1346c4125d64e09a5 /src | |
| parent | 1c9fc1e1dec67c8c05f02da1e0853439238c4d8e (diff) | |
| download | angular.js-42062dab34192d2cb9ed66a720c0f791408c61c0.tar.bz2 | |
refactor(scope): remove $flush/$observe ng:eval/ng:eval-order
Diffstat (limited to 'src')
| -rw-r--r-- | src/Angular.js | 4 | ||||
| -rw-r--r-- | src/Compiler.js | 126 | ||||
| -rw-r--r-- | src/Scope.js | 311 | ||||
| -rw-r--r-- | src/angular-bootstrap.js | 1 | ||||
| -rw-r--r-- | src/directives.js | 65 | ||||
| -rw-r--r-- | src/filters.js | 11 | ||||
| -rw-r--r-- | src/service/cookies.js | 2 | ||||
| -rw-r--r-- | src/service/defer.js | 4 | ||||
| -rw-r--r-- | src/service/location.js | 2 | ||||
| -rw-r--r-- | src/service/route.js | 6 | ||||
| -rw-r--r-- | src/service/updateView.js | 61 | ||||
| -rw-r--r-- | src/service/xhr.bulk.js | 2 | ||||
| -rw-r--r-- | src/service/xhr.js | 30 | ||||
| -rw-r--r-- | src/validators.js | 1 | ||||
| -rw-r--r-- | src/widgets.js | 26 | 
15 files changed, 76 insertions, 576 deletions
| diff --git a/src/Angular.js b/src/Angular.js index d698aa11..1b887779 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -77,10 +77,6 @@ var _undefined        = undefined,      NG_EXCEPTION      = 'ng-exception',      NG_VALIDATION_ERROR = 'ng-validation-error',      NOOP              = 'noop', -    PRIORITY_FIRST    = -99999, -    PRIORITY_WATCH    = -1000, -    PRIORITY_LAST     =  99999, -    PRIORITY          = {'FIRST': PRIORITY_FIRST, 'LAST': PRIORITY_LAST, 'WATCH':PRIORITY_WATCH},      Error             = window.Error,      /** holds major version number for IE or NaN for real browsers */      msie              = parseInt((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1], 10), diff --git a/src/Compiler.js b/src/Compiler.js index 8512f0c3..98e9630c 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -6,44 +6,26 @@   * bind to a new instance of elements. It also provides a list   * of child paths which contain child templates   */ -function Template(priority) { +function Template() {    this.paths = [];    this.children = []; -  this.inits = []; -  this.priority = priority; +  this.linkFns = [];    this.newScope = false;  }  Template.prototype = { -  attach: function(element, scope) { -    var inits = {}; -    this.collectInits(element, inits, scope); -    forEachSorted(inits, function(queue){ -      forEach(queue, function(fn) {fn();}); -    }); -  }, - -  collectInits: function(element, inits, scope) { -    var queue = inits[this.priority], childScope = scope; -    if (!queue) { -      inits[this.priority] = queue = []; -    } +  link: function(element, scope) { +    var childScope = scope;      if (this.newScope) {        childScope = isFunction(this.newScope) ? scope.$new(this.newScope(scope)) : scope.$new();        element.data($$scope, childScope);      } -    // TODO(misko): refactor this!!! -    // Why are inits even here? -    forEach(this.inits, function(fn) { -      queue.push(function() { -        childScope.$eval(function(){ -          try { -            return childScope.$service.invoke(childScope, fn, [element]); -          } catch (e) { -            childScope.$service('$exceptionHandler')(e); -          } -        }); -      }); +    forEach(this.linkFns, function(fn) { +      try { +        childScope.$service.invoke(childScope, fn, [element]); +      } catch (e) { +        childScope.$service('$exceptionHandler')(e); +      }      });      var i,          childNodes = element[0].childNodes, @@ -51,16 +33,16 @@ Template.prototype = {          paths = this.paths,          length = paths.length;      for (i = 0; i < length; i++) { -      children[i].collectInits(jqLite(childNodes[paths[i]]), inits, childScope); +      children[i].link(jqLite(childNodes[paths[i]]), childScope);      }    }, -  addInit:function(linkingFn) { +  addLinkFn:function(linkingFn) {      if (linkingFn) {        if (!linkingFn.$inject)          linkingFn.$inject = []; -      this.inits.push(linkingFn); +      this.linkFns.push(linkingFn);      }    }, @@ -73,7 +55,7 @@ Template.prototype = {    },    empty: function() { -    return this.inits.length === 0 && this.paths.length === 0; +    return this.linkFns.length === 0 && this.paths.length === 0;    }  }; @@ -211,7 +193,7 @@ Compiler.prototype = {          }        }      } -    template = this.templatize(templateElement, index, 0) || new Template(); +    template = this.templatize(templateElement, index) || new Template();      return function(scope, cloneConnectFn){        // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart        // and sometimes changes the structure of the DOM. @@ -222,69 +204,12 @@ Compiler.prototype = {        element.data($$scope, scope);        scope.$element = element;        (cloneConnectFn||noop)(element, scope); -      template.attach(element, scope); +      template.link(element, scope);        return scope;      };    }, - -  /** -   * @workInProgress -   * @ngdoc directive -   * @name angular.directive.ng:eval-order -   * @deprecated -   * -   * @description -   * Normally the view is updated from top to bottom. This usually is -   * not a problem, but under some circumstances the values for data -   * is not available until after the full view is computed. If such -   * values are needed before they are computed the order of -   * evaluation can be changed using ng:eval-order -   * -   * @element ANY -   * @param {integer|string=} [priority=0] priority integer, or FIRST, LAST constant -   * -   * @example -   * try changing the invoice and see that the Total will lag in evaluation -   * @example -     <doc:example> -       <doc:source> -        <div>TOTAL: without ng:eval-order {{ total | currency }}</div> -        <div ng:eval-order='LAST'>TOTAL: with ng:eval-order {{ total | currency }}</div> -        <table ng:init="items=[{qty:1, cost:9.99, desc:'gadget'}];total=0;"> -          <tr> -            <td>QTY</td> -            <td>Description</td> -            <td>Cost</td> -            <td>Total</td> -            <td></td> -          </tr> -          <tr ng:repeat="item in items"> -            <td><input name="item.qty"/></td> -            <td><input name="item.desc"/></td> -            <td><input name="item.cost"/></td> -            <td>{{item.qty * item.cost | currency}}</td> -            <td><a href="" ng:click="items.$remove(item)">X</a></td> -          </tr> -          <tr> -            <td colspan="3"><a href="" ng:click="items.$add()">add</a></td> -            <td>{{ total = items.$sum('qty*cost') | currency }}</td> -          </tr> -        </table> -       </doc:source> -       <doc:scenario> -         it('should check ng:format', function(){ -           expect(using('.doc-example-live div:first').binding("total")).toBe('$0.00'); -           expect(using('.doc-example-live div:last').binding("total")).toBe('$9.99'); -           input('item.qty').enter('2'); -           expect(using('.doc-example-live div:first').binding("total")).toBe('$9.99'); -           expect(using('.doc-example-live div:last').binding("total")).toBe('$19.98'); -         }); -       </doc:scenario> -     </doc:example> -   */ - -  templatize: function(element, elementIndex, priority){ +  templatize: function(element, elementIndex){      var self = this,          widget,          fn, @@ -300,17 +225,8 @@ Compiler.prototype = {            directives: function(value){ if(isDefined(value)) directives = value; return directives;},            scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value; return template.newScope;}          }; -    try { -      priority = element.attr('ng:eval-order') || priority || 0; -    } catch (e) { -      // for some reason IE throws error under some weird circumstances. so just assume nothing -      priority = priority || 0; -    }      element.addClass(elementNamespace); -    if (isString(priority)) { -      priority = PRIORITY[uppercase(priority)] || parseInt(priority, 10); -    } -    template = new Template(priority); +    template = new Template();      eachAttribute(element, function(value, name){        if (!widget) {          if (widget = self.widgets('@' + name)) { @@ -330,7 +246,7 @@ Compiler.prototype = {        descend = false;        directives = false;        var parent = element.parent(); -      template.addInit(widget.call(selfApi, element)); +      template.addLinkFn(widget.call(selfApi, element));        if (parent && parent[0]) {          element = jqLite(parent[0].childNodes[elementIndex]);        } @@ -361,14 +277,14 @@ Compiler.prototype = {          fn = directiveFns[name];          if (fn) {            element.addClass('ng-directive'); -          template.addInit((directiveFns[name]).call(selfApi, value, element)); +          template.addLinkFn((directiveFns[name]).call(selfApi, value, element));          }        });      }      // Process non text child nodes      if (descend) {        eachNode(element, function(child, i){ -        template.addChild(i, self.templatize(child, i, priority)); +        template.addChild(i, self.templatize(child, i));        });      }      return template.empty() ? null : template; diff --git a/src/Scope.js b/src/Scope.js index 370c4bec..bd402744 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -93,7 +93,7 @@ function createScope(providers, instanceCache) {   */  function Scope() {    this.$id = nextUid(); -  this.$$phase = this.$parent = this.$$watchers = this.$$observers = +  this.$$phase = this.$parent = this.$$watchers =      this.$$nextSibling = this.$$childHead = this.$$childTail = null;    this['this'] = this.$root =  this;  } @@ -145,7 +145,7 @@ Scope.prototype = {     * @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.$flush $flush()} events. The scope can be removed from the scope +   * {@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 @@ -168,7 +168,7 @@ Scope.prototype = {      child['this'] = child;      child.$parent = this;      child.$id = nextUid(); -    child.$$phase = child.$$watchers = child.$$observers = +    child.$$phase = child.$$watchers =        child.$$nextSibling = child.$$childHead = child.$$childTail = null;      if (this.$$childHead) {        this.$$childTail.$$nextSibling = child; @@ -207,76 +207,15 @@ Scope.prototype = {     *   {@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 achieving my rerunning the watchers until no changes are detected. The rerun iteration +   *   is achieved by rerunning the watchers until no changes are detected. The rerun iteration     *   limit is 100 to prevent infinity loop deadlock.     * -   * # When to use `$watch`? -   * -   * The `$watch` should be used from within controllers to listen on properties *immediately* after -   * a stimulus is applied to the system (see {@link angular.scope.$apply $apply()}). This is in -   * contrast to {@link angular.scope.$observe $observe()} which is used from within the directives -   * and which gets applied at some later point in time. In addition -   * {@link angular.scope.$observe $observe()} must not modify the model.     *     * 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.)     * -   * # `$watch` vs `$observe` -   * -   * <table class="table"> -   *   <tr> -   *     <th></td> -   *     <th>{@link angular.scope.$watch $watch()}</th> -   *     <th>{@link angular.scope.$observe $observe()}</th> -   *   </tr> -   *   <tr><th colspan="3" class="section">When to use it?</th></tr> -   *   <tr> -   *     <th>Purpose</th> -   *     <td>Application behavior (including further model mutation) in response to a model -   *         mutation.</td> -   *     <td>Update the DOM in response to a model mutation.</td> -   *   </tr> -   *   <tr> -   *     <th>Used from</th> -   *     <td>{@link angular.directive.ng:controller controller}</td> -   *     <td>{@link angular.directive directives}</td> -   *   </tr> -   *   <tr><th colspan="3" class="section">What fires listeners?</th></tr> -   *   <tr> -   *     <th>Directly</th> -   *     <td>{@link angular.scope.$digest $digest()}</td> -   *     <td>{@link angular.scope.$flush $flush()}</td> -   *   </tr> -   *   <tr> -   *     <th>Indirectly via {@link angular.scope.$apply $apply()}</th> -   *     <td>{@link angular.scope.$apply $apply} calls -   *         {@link angular.scope.$digest $digest()} after apply argument executes.</td> -   *     <td>{@link angular.scope.$apply $apply} schedules -   *         {@link angular.scope.$flush $flush()} at some future time via -   *         {@link angular.service.$updateView $updateView}</td> -   *   </tr> -   *   <tr><th colspan="3" class="section">API contract</th></tr> -   *   <tr> -   *     <th>Model mutation</th> -   *     <td>allowed: detecting mutations requires one or mare calls to `watchExpression' per -   *         {@link angular.scope.$digest $digest()} cycle</td> -   *     <td>not allowed: called once per {@link angular.scope.$flush $flush()} must be -   *         {@link http://en.wikipedia.org/wiki/Idempotence idempotent} -   *         (function without side-effects which can be called multiple times.)</td> -   *   </tr> -   *   <tr> -   *     <th>Initial Value</th> -   *     <td>uses the current value of `watchExpression` as the initial value. Does not fire on -   *         initial call to {@link angular.scope.$digest $digest()}, unless `watchExpression` has -   *         changed form the initial value.</td> -   *     <td>fires on first run of {@link angular.scope.$flush $flush()} regardless of value of -   *         `observeExpression`</td> -   *   </tr> -   * </table> -   * -   *     *     * # Example       <pre> @@ -311,8 +250,6 @@ Scope.prototype = {     *    - `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()} a function which will call the `listener` with apprariate arguments. -   *    Useful for forcing initialization of listener.     */    $watch: function(watchExp, listener) {      var scope = this; @@ -326,15 +263,9 @@ Scope.prototype = {      // the while loop reads in reverse order.      array.unshift({        fn: listenFn, -      last: copy(get(scope)), +      last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run.        get: get      }); -    // we only return the initialization function for $watch (not for $observe), since creating -    // function cost time and memory, and $observe functions do not need it. -    return function() { -      var value = get(scope); -      listenFn(scope, value, value); -    };    },    /** @@ -369,15 +300,15 @@ Scope.prototype = {         scope.counter = 0;         expect(scope.counter).toEqual(0); -       scope.$flush('name', function(scope, newValue, oldValue) { counter = counter + 1; }); +       scope.$digest('name', function(scope, newValue, oldValue) { counter = counter + 1; });         expect(scope.counter).toEqual(0); -       scope.$flush(); +       scope.$digest();         // no variable change         expect(scope.counter).toEqual(0);         scope.name = 'adam'; -       scope.$flush(); +       scope.$digest();         expect(scope.counter).toEqual(1);       </pre>     * @@ -432,222 +363,14 @@ Scope.prototype = {    /**     * @workInProgress     * @ngdoc function -   * @name angular.scope.$observe -   * @function -   * -   * @description -   * Registers a `listener` callback to be executed during the {@link angular.scope.$flush $flush()} -   * phase when the `observeExpression` changes.. -   * -   * - The `observeExpression` is called on every call to {@link angular.scope.$flush $flush()} and -   *   should return the value which will be observed. -   * - The `listener` is called only when the value from the current `observeExpression` and the -   *   previous call to `observeExpression' 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. -   * -   * # When to use `$observe`? -   * -   * {@link angular.scope.$observe $observe()} is used from within directives and gets applied at -   * some later point in time. Addition {@link angular.scope.$observe $observe()} must not -   * modify the model. This is in contrast to {@link angular.scope.$watch $watch()} which should be -   * used from within controllers to trigger a callback *immediately* after a stimulus is applied -   * to the system (see {@link angular.scope.$apply $apply()}). -   * -   * If you want to be notified whenever {@link angular.scope.$flush $flush} is called, -   * you can register an `observeExpression` function with no `listener`. -   * -   * -   * # `$watch` vs `$observe` -   * -   * <table class="table"> -   *   <tr> -   *     <th></td> -   *     <th>{@link angular.scope.$watch $watch()}</th> -   *     <th>{@link angular.scope.$observe $observe()}</th> -   *   </tr> -   *   <tr><th colspan="3" class="section">When to use it?</th></tr> -   *   <tr> -   *     <th>Purpose</th> -   *     <td>Application behavior (including further model mutation) in response to a model -   *         mutation.</td> -   *     <td>Update the DOM in response to a model mutation.</td> -   *   </tr> -   *   <tr> -   *     <th>Used from</th> -   *     <td>{@link angular.directive.ng:controller controller}</td> -   *     <td>{@link angular.directive directives}</td> -   *   </tr> -   *   <tr><th colspan="3" class="section">What fires listeners?</th></tr> -   *   <tr> -   *     <th>Directly</th> -   *     <td>{@link angular.scope.$digest $digest()}</td> -   *     <td>{@link angular.scope.$flush $flush()}</td> -   *   </tr> -   *   <tr> -   *     <th>Indirectly via {@link angular.scope.$apply $apply()}</th> -   *     <td>{@link angular.scope.$apply $apply} calls -   *         {@link angular.scope.$digest $digest()} after apply argument executes.</td> -   *     <td>{@link angular.scope.$apply $apply} schedules -   *         {@link angular.scope.$flush $flush()} at some future time via -   *         {@link angular.service.$updateView $updateView}</td> -   *   </tr> -   *   <tr><th colspan="3" class="section">API contract</th></tr> -   *   <tr> -   *     <th>Model mutation</th> -   *     <td>allowed: detecting mutations requires one or mare calls to `watchExpression' per -   *         {@link angular.scope.$digest $digest()} cycle</td> -   *     <td>not allowed: called once per {@link angular.scope.$flush $flush()} must be -   *         {@link http://en.wikipedia.org/wiki/Idempotence idempotent} -   *         (function without side-effects which can be called multiple times.)</td> -   *   </tr> -   *   <tr> -   *     <th>Initial Value</th> -   *     <td>uses the current value of `watchExpression` as the initial value. Does not fire on -   *         initial call to {@link angular.scope.$digest $digest()}, unless `watchExpression` has -   *         changed form the initial value.</td> -   *     <td>fires on first run of {@link angular.scope.$flush $flush()} regardless of value of -   *         `observeExpression`</td> -   *   </tr> -   * </table> -   * -   * # Example -     <pre> -       var scope = angular.scope(); -       scope.name = 'misko'; -       scope.counter = 0; - -       expect(scope.counter).toEqual(0); -       scope.$flush('name', function(scope, newValue, oldValue) { counter = counter + 1; }); -       expect(scope.counter).toEqual(0); - -       scope.$flush(); -       // no variable change -       expect(scope.counter).toEqual(0); - -       scope.name = 'adam'; -       scope.$flush(); -       expect(scope.counter).toEqual(1); -     </pre> -   * -   * @param {(function()|string)} observeExpression Expression that is evaluated on each -   *    {@link angular.scope.$flush $flush} 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 `observeExpression` 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. -   */ -  $observe: function(watchExp, listener) { -    var array = this.$$observers; - -    if (!array) { -      array = this.$$observers = []; -    } -    // we use unshift since we use a while loop in $flush for speed. -    // the while loop reads in reverse order. -    array.unshift({ -      fn: compileToFn(listener || noop, 'listener'), -      last: NaN, -      get:  compileToFn(watchExp, 'watch') -    }); -  }, - -  /** -   * @workInProgress -   * @ngdoc function -   * @name angular.scope.$flush -   * @function -   * -   * @description -   * Process all of the {@link angular.scope.$observe observers} of the current scope -   * and its children. -   * -   * Usually you don't call `$flush()` 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 scheduled a call to `$flush()` (with the -   * help of the {@link angular.service.$updateView $updateView} service). -   * -   * If you want to be notified whenever `$flush()` is called, -   * you can register a `observeExpression` function  with {@link angular.scope.$observe $observe()} -   * with no `listener`. -   * -   * You may have a need to call `$flush()` 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.$flush('name', function(scope, newValue, oldValue) { counter = counter + 1; }); -       expect(scope.counter).toEqual(0); - -       scope.$flush(); -       // no variable change -       expect(scope.counter).toEqual(0); - -       scope.name = 'adam'; -       scope.$flush(); -       expect(scope.counter).toEqual(1); -     </pre> -   * -   */ -  $flush: function() { -    var observers = this.$$observers, -        child, -        length, -        observer, value, last; - -    if (this.$$phase) { -      throw Error(this.$$phase + ' already in progress'); -    } -    this.$$phase = '$flush'; -    if (observers) { -      // process our watches -      length = observers.length; -      while (length--) { -        try { -          observer = observers[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 = observer.get(this)) !== (last = observer.last) && !equals(value, last)) { -            observer.fn(this, observer.last = copy(value), last); -          } -        } catch (e){ -          this.$service('$exceptionHandler')(e); -        } -      } -    } -    // observers can create new children -    child = this.$$childHead; -    while(child) { -      child.$flush(); -      child = child.$$nextSibling; -    } -    this.$$phase = null; -  }, - -  /** -   * @workInProgress -   * @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()} and -   * {@link angular.scope.$flush $flush()} will no longer propagate to the current scope and its -   * children. Removal also implies that the current scope is eligible for garbage collection. +   * 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 `$destroy()` is usually used by directives such as     * {@link angular.widget.@ng:repeat ng:repeat} for managing the unrolling of the loop. @@ -726,9 +449,7 @@ Scope.prototype = {     * (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} and scheduling -   * {@link angular.service.$updateView updating of the view} which in turn -   * {@link angular.scope.$digest executes observers} to update the DOM. +   * {@link angular.scope.$digest executing watches}.     *     * ## Life cycle     * @@ -740,7 +461,6 @@ Scope.prototype = {            $exceptionHandler(e);          } finally {            $root.$digest(); -          $updateView();          }        }     * @@ -753,12 +473,6 @@ Scope.prototype = {     *    {@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. -   * 4. A DOM update is scheduled using the {@link angular.service.$updateView $updateView} service. -   *    The `$updateView` may merge multiple update requests into a single update, if the requests -   *    are issued in close time proximity. -   * 6. The {@link angular.service.$updateView $updateView} service then fires DOM -   *    {@link angular.scope.$observe observers} using the {@link angular.scope.$flush $flush()} -   *    method.     *     *     * @param {(string|function())=} exp An angular expression to be executed. @@ -775,7 +489,6 @@ Scope.prototype = {        this.$service('$exceptionHandler')(e);      } finally {        this.$root.$digest(); -      this.$service('$updateView')();      }    }  }; diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index 9c95a158..7495b6ad 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -114,7 +114,6 @@               'service/log.js',               'service/resource.js',               'service/route.js', -             'service/updateView.js',               'service/window.js',               'service/xhr.bulk.js',               'service/xhr.cache.js', diff --git a/src/directives.js b/src/directives.js index 4712f250..dd292de8 100644 --- a/src/directives.js +++ b/src/directives.js @@ -28,9 +28,6 @@   * * {@link angular.directive.ng:click ng:click} - Executes custom behavior when element is clicked.   * * {@link angular.directive.ng:controller ng:controller} - Creates a scope object linked to the   * DOM element and assigns behavior to the scope. - * * {@link angular.directive.ng:eval ng:eval} - Executes a binding but blocks output. - * * {@link angular.directive.ng:eval-order ng:eval-order} - Change evaluation order when updating - * the view.   * * {@link angular.directive.ng:hide ng:hide} - Conditionally hides a portion of HTML.   * * {@link angular.directive.ng:href ng:href} - Places an href in the angular namespace.   * * {@link angular.directive.ng:init} - Initialization tasks run before a template is executed. @@ -177,54 +174,6 @@ angularDirective("ng:controller", function(expression){  /**   * @workInProgress - * @deprecated - * @ngdoc directive - * @name angular.directive.ng:eval - * - * @description - * The `ng:eval` allows you to execute a binding which has side effects - * without displaying the result to the user. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. - * - * @example - * Notice that `{{` `obj.multiplied = obj.a * obj.b` `}}` has a side effect of assigning - * a value to `obj.multiplied` and displaying the result to the user. Sometimes, - * however, it is desirable to execute a side effect without showing the value to - * the user. In such a case `ng:eval` allows you to execute code without updating - * the display. -   <doc:example> -     <doc:source> -       <input name="obj.a" value="6" > -         * <input name="obj.b" value="2"> -         = {{obj.multiplied = obj.a * obj.b}} <br> -       <span ng:eval="obj.divide = obj.a / obj.b"></span> -       <span ng:eval="obj.updateCount = 1 + (obj.updateCount||0)"> -       </span> -       <tt>obj.divide = {{obj.divide}}</tt><br> -       <tt>obj.updateCount = {{obj.updateCount}}</tt> -     </doc:source> -     <doc:scenario> -       it('should check eval', function(){ -         expect(binding('obj.divide')).toBe('3'); -         expect(binding('obj.updateCount')).toBe('1'); -         input('obj.a').enter('12'); -         expect(binding('obj.divide')).toBe('6'); -         expect(binding('obj.updateCount')).toBe('2'); -       }); -     </doc:scenario> -   </doc:example> - */ -// TODO(misko): remove me -angularDirective("ng:eval", function(expression){ -  return function(element){ -    this.$observe(expression); -  }; -}); - -/** - * @workInProgress   * @ngdoc directive   * @name angular.directive.ng:bind   * @@ -258,7 +207,7 @@ angularDirective("ng:bind", function(expression, element){    element.addClass('ng-binding');    return function(element) {      var lastValue = noop, lastError = noop; -    this.$observe(function(scope) { +    this.$watch(function(scope) {        // TODO(misko): remove error handling https://github.com/angular/angular.js/issues/347        var error, value, html, isHtml, isDomElement,            hadOwnElement = scope.hasOwnProperty('$element'), @@ -398,7 +347,7 @@ angularDirective("ng:bind-template", function(expression, element){    var templateFn = compileBindTemplate(expression);    return function(element) {      var lastValue; -    this.$observe(function(scope) { +    this.$watch(function(scope) {        var value = templateFn(scope, element, true);        if (value != lastValue) {          element.text(value); @@ -472,7 +421,7 @@ var REMOVE_ATTRIBUTES = {  angularDirective("ng:bind-attr", function(expression){    return function(element){      var lastValue = {}; -    this.$observe(function(scope){ +    this.$watch(function(scope){        var values = scope.$eval(expression);        for(var key in values) {          var value = compileBindTemplate(values[key])(scope, element), @@ -594,7 +543,7 @@ function ngClass(selector) {    return function(expression, element) {      var existing = element[0].className + ' ';      return function(element) { -      this.$observe(function(scope) { +      this.$watch(function(scope) {          if (selector(scope.$index)) {            var value = scope.$eval(expression);            if (isArray(value)) value = value.join(' '); @@ -756,7 +705,7 @@ angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));   */  angularDirective("ng:show", function(expression, element){    return function(element){ -    this.$observe(expression, function(scope, value){ +    this.$watch(expression, function(scope, value){        toBoolean(value) ? element.show() : element.hide();      });    }; @@ -797,7 +746,7 @@ angularDirective("ng:show", function(expression, element){   */  angularDirective("ng:hide", function(expression, element){    return function(element){ -    this.$observe(expression, function(scope, value){ +    this.$watch(expression, function(scope, value){        toBoolean(value) ? element.hide() : element.show();      });    }; @@ -839,7 +788,7 @@ angularDirective("ng:hide", function(expression, element){  angularDirective("ng:style", function(expression, element){    return function(element){      var resetStyle = getStyle(element); -    this.$observe(function(scope){ +    this.$watch(function(scope){        var style = scope.$eval(expression) || {}, key, mergedStyle = {};        for(key in style) {          if (resetStyle[key] === undefined) resetStyle[key] = ''; diff --git a/src/filters.js b/src/filters.js index 52aafcf3..e423f590 100644 --- a/src/filters.js +++ b/src/filters.js @@ -435,18 +435,11 @@ angularFilter.date = function(date, format) {   * @example:     <doc:example>       <doc:source> -       <input type="text" name="objTxt" value="{a:1, b:[]}" -              ng:eval="obj = $eval(objTxt)"/> -       <pre>{{ obj | json }}</pre> +       <pre>{{ {'name':'value'} | json }}</pre>       </doc:source>       <doc:scenario>         it('should jsonify filtered objects', function() { -         expect(binding('obj | json')).toBe('{\n  "a":1,\n  "b":[]}'); -       }); - -       it('should update', function() { -         input('objTxt').enter('[1, 2, 3]'); -         expect(binding('obj | json')).toBe('[1,2,3]'); +         expect(binding('| json')).toBe('{\n  "name":"value"}');         });       </doc:scenario>     </doc:example> diff --git a/src/service/cookies.js b/src/service/cookies.js index 74e63679..220a6ed5 100644 --- a/src/service/cookies.js +++ b/src/service/cookies.js @@ -37,7 +37,7 @@ angularServiceInject('$cookies', function($browser) {    //at the end of each eval, push cookies    //TODO: this should happen before the "delayed" watches fire, because if some cookies are not    //      strings or browser refuses to store some cookies, we update the model in the push fn. -  this.$observe(push); +  this.$watch(push);    return cookies; diff --git a/src/service/defer.js b/src/service/defer.js index 0a69912c..1ea4bf0c 100644 --- a/src/service/defer.js +++ b/src/service/defer.js @@ -5,8 +5,6 @@   * @ngdoc service   * @name angular.service.$defer   * @requires $browser - * @requires $exceptionHandler - * @requires $updateView   *   * @description   * Delegates to {@link angular.service.$browser $browser.defer}, but wraps the `fn` function @@ -25,4 +23,4 @@ angularServiceInject('$defer', function($browser) {        scope.$apply(fn);      }, delay);    }; -}, ['$browser', '$exceptionHandler', '$updateView']); +}, ['$browser']); diff --git a/src/service/location.js b/src/service/location.js index 23531140..6e646b8b 100644 --- a/src/service/location.js +++ b/src/service/location.js @@ -91,7 +91,7 @@ angularServiceInject("$location", function($browser) {     * @description     * Updates the location object.     * Does not immediately update the browser -   * Browser is updated at the end of $flush() +   * Browser is updated at the end of $digest()     *     * Does not immediately update the browser. Instead the browser is updated at the end of $eval()     * cycle. diff --git a/src/service/route.js b/src/service/route.js index e1d0e7be..9b4db0dd 100644 --- a/src/service/route.js +++ b/src/service/route.js @@ -62,7 +62,7 @@        </doc:scenario>      </doc:example>   */ -angularServiceInject('$route', function($location, $updateView) { +angularServiceInject('$route', function($location) {    var routes = {},        onChange = [],        matcher = switchRouteMatcher, @@ -267,7 +267,7 @@ angularServiceInject('$route', function($location, $updateView) {    } -  this.$watch(function(){return dirty + $location.hash;}, updateRoute)(); +  this.$watch(function(){return dirty + $location.hash;}, updateRoute);    return $route; -}, ['$location', '$updateView']); +}, ['$location']); diff --git a/src/service/updateView.js b/src/service/updateView.js deleted file mode 100644 index b51e719b..00000000 --- a/src/service/updateView.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$updateView - * @requires $browser - * - * @description - * Calling `$updateView` enqueues the eventual update of the view. (Update the DOM to reflect the - * model). The update is eventual, since there are often multiple updates to the model which may - * be deferred. The default update delayed is 25 ms. This means that the view lags the model by - * that time. (25ms is small enough that it is perceived as instantaneous by the user). The delay - * can be adjusted by setting the delay property of the service. - * - * <pre>angular.service('$updateView').delay = 10</pre> - * - * The delay is there so that multiple updates to the model which occur sufficiently close - * together can be merged into a single update. - * - * You don't usually call '$updateView' directly since angular does it for you in most cases, - * but there are some cases when you need to call it. - * - *  - `$updateView()` called automatically by angular: - *    - Your Application Controllers: Your controller code is called by angular and hence - *      angular is aware that you may have changed the model. - *    - Your Services: Your service is usually called by your controller code, hence same rules - *      apply. - *  - May need to call `$updateView()` manually: - *    - Widgets / Directives: If you listen to any DOM events or events on any third party - *      libraries, then angular is not aware that you may have changed state state of the - *      model, and hence you need to call '$updateView()' manually. - *    - 'setTimeout'/'XHR':  If you call 'setTimeout' (instead of {@link angular.service.$defer}) - *      or 'XHR' (instead of {@link angular.service.$xhr}) then you may be changing the model - *      without angular knowledge and you may need to call '$updateView()' directly. - * - * Note: if you wish to update the view immediately (without delay), you can do so by calling - * {@link angular.scope.$apply} at any time from your code: - * <pre>scope.$apply()</pre> - * - * In unit-test mode the update is instantaneous and synchronous to simplify writing tests. - * - */ - -function serviceUpdateViewFactory($browser){ -  var rootScope = this; -  var scheduled; -  function update(){ -    scheduled = false; -    rootScope.$flush(); -  } -  return $browser.isMock ? update : function(){ -    if (!scheduled) { -      scheduled = true; -      $browser.defer(update, serviceUpdateViewFactory.delay); -    } -  }; -} -serviceUpdateViewFactory.delay = 25; - -angularServiceInject('$updateView', serviceUpdateViewFactory, ['$browser']); diff --git a/src/service/xhr.bulk.js b/src/service/xhr.bulk.js index 816336f8..15bf0c83 100644 --- a/src/service/xhr.bulk.js +++ b/src/service/xhr.bulk.js @@ -82,6 +82,6 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){        }      });    }; -  this.$observe(bulkXHR.flush); +  this.$watch(bulkXHR.flush);    return bulkXHR;  }, ['$xhr', '$xhr.error', '$log']); diff --git a/src/service/xhr.js b/src/service/xhr.js index 224bc57a..bf882684 100644 --- a/src/service/xhr.js +++ b/src/service/xhr.js @@ -10,8 +10,6 @@   *                    in your tests   * @requires $xhr.error $xhr delegates all non `2xx` response code to this service.   * @requires $log $xhr delegates all exceptions to `$log.error()`. - * @requires $updateView After a server response the view needs to be updated for data-binding to - *           take effect.   *   * @description   * Generates an XHR request. The $xhr service delegates all requests to @@ -173,8 +171,8 @@       </doc:scenario>     </doc:example>   */ -angularServiceInject('$xhr', function($browser, $error, $log, $updateView){ - +angularServiceInject('$xhr', function($browser, $error, $log){ +  var rootScope = this;    var xhrHeaderDefaults = {      common: {        "Accept": "application/json, text/plain, */*", @@ -206,19 +204,19 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){              response = fromJson(response, true);            }          } -        if (200 <= code && code < 300) { -          success(code, response); -        } else if (isFunction(error)) { -          error(code, response); -        } else { -          $error( -            {method: method, url: url, data: post, success: success}, -            {status: code, body: response}); -        } +        rootScope.$apply(function(){ +          if (200 <= code && code < 300) { +              success(code, response); +          } else if (isFunction(error)) { +            error(code, response); +          } else { +            $error( +              {method: method, url: url, data: post, success: success}, +              {status: code, body: response}); +          } +        });        } catch (e) {          $log.error(e); -      } finally { -        $updateView();        }      }, extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},                xhrHeaderDefaults.common, @@ -228,4 +226,4 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){    xhr.defaults = {headers: xhrHeaderDefaults};    return xhr; -}, ['$browser', '$xhr.error', '$log', '$updateView']); +}, ['$browser', '$xhr.error', '$log']); diff --git a/src/validators.js b/src/validators.js index 6b54f67a..0a69a002 100644 --- a/src/validators.js +++ b/src/validators.js @@ -469,7 +469,6 @@ extend(angularValidator, {            $invalidWidgets.markValid(element);          }          element.data($$validate)(); -        scope.$service('$updateView')();        });      } else if (inputState.inFlight) {        // request in flight, mark widget invalid, but don't show it to user diff --git a/src/widgets.js b/src/widgets.js index 438fccb3..3d034fc5 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -548,7 +548,7 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn, textBox) {          if (!equals(lastValue, value)) {            view.set(lastValue = value);          } -      })(); +      });      }    });  } @@ -773,7 +773,7 @@ angularWidget('select', function(element){        }      }); -    scope.$observe(function(scope) { +    scope.$watch(function(scope) {        var optionGroups = {'':[]}, // Temporary location for the option groups before we render them            optionGroupNames = [''],            optionGroupName, @@ -996,15 +996,15 @@ angularWidget('ng:include', function(element){            oldScope;        function incrementChange(){ changeCounter++;} -      this.$observe(srcExp, incrementChange); -      this.$observe(function(scope){ +      this.$watch(srcExp, incrementChange); +      this.$watch(function(scope){          var newScope = scope.$eval(scopeExp);          if (newScope !== oldScope) {            oldScope = newScope;            incrementChange();          }        }); -      this.$observe(function(){return changeCounter;}, function(scope) { +      this.$watch(function(){return changeCounter;}, function(scope) {          var src = scope.$eval(srcExp),              useScope = scope.$eval(scopeExp); @@ -1124,9 +1124,9 @@ angularWidget('ng:switch', function (element) {          childScope = scope.$new();          childScope.$eval(changeExpr);        } -    })(); +    }); -    this.$observe(function(){return changeCounter;}, function() { +    this.$watch(function(){return changeCounter;}, function() {        element.html('');        if (selectedTemplate) {          selectedTemplate(childScope, function(caseElement) { @@ -1251,7 +1251,7 @@ angularWidget('@ng:repeat', function(expression, element){      var childScopes = [];      var childElements = [iterStartElement];      var parentScope = this; -    this.$observe(function(scope){ +    this.$watch(function(scope){        var index = 0,            childCount = childScopes.length,            collection = scope.$eval(rhs), @@ -1285,11 +1285,11 @@ angularWidget('@ng:repeat', function(expression, element){              linker(childScope, function(clone){                clone.attr('ng:repeat-index', index);                fragment.appendChild(clone[0]); -              // TODO(misko): Temporary hack - maybe think about it - removed after we add fragment after $flush() -              // This causes double $flush for children +              // TODO(misko): Temporary hack - maybe think about it - removed after we add fragment after $digest() +              // This causes double $digest for children                // The first flush will couse a lot of DOM access (initial)                // Second flush shuld be noop since nothing has change hence no DOM access. -              childScope.$flush(); +              childScope.$digest();                childElements[index + 1] = clone;              });            } @@ -1300,7 +1300,7 @@ angularWidget('@ng:repeat', function(expression, element){        //attach new nodes buffered in doc fragment        if (addFragmentTo) {          // TODO(misko): For performance reasons, we should do the addition after all other widgets -        // have run. For this should happend after $flush() is done! +        // have run. For this should happend after $digest() is done!          addFragmentTo.after(jqLite(fragment));        } @@ -1429,7 +1429,7 @@ angularWidget('ng:view', function(element) {        })(); //initialize the state forcefully, it's possible that we missed the initial              //$route#onChange already -      this.$observe(function(){return changeCounter;}, function() { +      this.$watch(function(){return changeCounter;}, function() {          var template = $route.current && $route.current.template;          if (template) {            //xhr's callback must be async, see commit history for more info | 
