diff options
| author | Igor Minar | 2014-03-17 15:48:09 -0700 | 
|---|---|---|
| committer | Igor Minar | 2014-03-18 12:00:50 -0700 | 
| commit | 78057a945ef84cbb05f9417fe884cb8c28e67b44 (patch) | |
| tree | cd8f7dc84b4f4188684649f5d68bf7106c43e906 /src/ng/rootScope.js | |
| parent | c5e41a0325d0476f8e0f02fce3b0712050e2571b (diff) | |
| download | angular.js-78057a945ef84cbb05f9417fe884cb8c28e67b44.tar.bz2 | |
fix(Scope): $watchCollection should call listener with oldValue
Originally we destroyed the oldValue by incrementaly copying over portions of the newValue
into the oldValue during dirty-checking, this resulted in oldValue to be equal to newValue
by the time we called the watchCollection listener.
The fix creates a copy of the newValue each time a change is detected and then uses that
copy *the next time* a change is detected.
To make `$watchCollection` behave the same way as `$watch`, during the first iteration
the listener is called with newValue and oldValue being identical.
Since many of the corner-cases are already covered by existing tests, I refactored the
test logging to include oldValue and made the tests more readable.
Closes #2621
Closes #5661
Closes #5688
Closes #6736
Diffstat (limited to 'src/ng/rootScope.js')
| -rw-r--r-- | src/ng/rootScope.js | 51 | 
1 files changed, 43 insertions, 8 deletions
| diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index 2bb965bb..ce0f8ad5 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -398,30 +398,40 @@ function $RootScopeProvider(){         *    {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the         *    collection will trigger a call to the `listener`.         * -       * @param {function(newCollection, oldCollection, scope)} listener a callback function that is -       *    fired with both the `newCollection` and `oldCollection` as parameters. -       *    The `newCollection` object is the newly modified data obtained from the `obj` expression -       *    and the `oldCollection` object is a copy of the former collection data. -       *    The `scope` refers to the current scope. +       * @param {function(newCollection, oldCollection, scope)} listener a callback function called +       *    when a change is detected. +       *    - The `newCollection` object is the newly modified data obtained from the `obj` expression +       *    - The `oldCollection` object is a copy of the former collection data. +       *      Due to performance considerations, the`oldCollection` value is computed only if the +       *      `listener` function declares two or more arguments. +       *    - The `scope` argument refers to the current scope.         *         * @returns {function()} Returns a de-registration function for this listener. When the         *    de-registration function is executed, the internal watch operation is terminated.         */        $watchCollection: function(obj, listener) {          var self = this; -        var oldValue; +        // the current value, updated on each dirty-check run          var newValue; +        // a shallow copy of the newValue from the last dirty-check run, +        // updated to match newValue during dirty-check run +        var oldValue; +        // a shallow copy of the newValue from when the last change happened +        var veryOldValue; +        // only track veryOldValue if the listener is asking for it +        var trackVeryOldValue = (listener.length > 1);          var changeDetected = 0;          var objGetter = $parse(obj);          var internalArray = [];          var internalObject = {}; +        var initRun = true;          var oldLength = 0;          function $watchCollectionWatch() {            newValue = objGetter(self);            var newLength, key; -          if (!isObject(newValue)) { +          if (!isObject(newValue)) { // if primitive              if (oldValue !== newValue) {                oldValue = newValue;                changeDetected++; @@ -487,7 +497,32 @@ function $RootScopeProvider(){          }          function $watchCollectionAction() { -          listener(newValue, oldValue, self); +          if (initRun) { +            initRun = false; +            listener(newValue, newValue, self); +          } else { +            listener(newValue, veryOldValue, self); +          } + +          // make a copy for the next time a collection is changed +          if (trackVeryOldValue) { +            if (!isObject(newValue)) { +              //primitive +              veryOldValue = newValue; +            } else if (isArrayLike(newValue)) { +              veryOldValue = new Array(newValue.length); +              for (var i = 0; i < newValue.length; i++) { +                veryOldValue[i] = newValue[i]; +              } +            } else { // if object +              veryOldValue = {}; +              for (var key in newValue) { +                if (hasOwnProperty.call(newValue, key)) { +                  veryOldValue[key] = newValue[key]; +                } +              } +            } +          }          }          return this.$watch($watchCollectionWatch, $watchCollectionAction); | 
