From d6e3e1baabc3acc930e4fda387b62cbd03e64577 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 23 Feb 2012 15:01:08 -0800 Subject: [PATCH] feta(scope): watch object refference or equality Breaks: Must set $watch equality to true for the old behavior --- src/directives.js | 4 ++-- src/service/scope.js | 28 +++++++++++++++++++++------ test/service/scopeSpec.js | 40 +++++++++++++++++++++------------------ 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/directives.js b/src/directives.js index 67c9f1f60d02..8a2af70449b9 100644 --- a/src/directives.js +++ b/src/directives.js @@ -592,7 +592,7 @@ function classDirective(name, selector) { if (isObject(newVal) && !isArray(newVal)) newVal = map(newVal, function(v, k) { if (v) return k }); if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal); } - }); + }, true); }); } @@ -837,7 +837,7 @@ var ngStyleDirective = valueFn(function(scope, element, attr) { forEach(oldStyles, function(val, style) { element.css(style, '');}); } if (newStyles) element.css(newStyles); - }); + }, true); }); diff --git a/src/service/scope.js b/src/service/scope.js index 2d2f7fec0b6d..79708f0c3e53 100644 --- a/src/service/scope.js +++ b/src/service/scope.js @@ -35,6 +35,15 @@ * event processing life-cycle. See {@link guide/dev_guide.scopes developer guide on scopes}. */ function $RootScopeProvider(){ + var TTL = 10; + + this.ttl = function(value) { + if (arguments.length) { + TTL = value; + } + return TTL; + } + this.$get = ['$injector', '$exceptionHandler', '$parse', function( $injector, $exceptionHandler, $parse) { @@ -248,9 +257,11 @@ function $RootScopeProvider(){ * * - `string`: Evaluated as {@link guide/dev_guide.expressions expression} * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters. + * + * @param {boolean=} objectEquality Compare object for equality rather then for refference. * @returns {function()} Returns a deregistration function for this listener. */ - $watch: function(watchExp, listener) { + $watch: function(watchExp, listener, objectEquality) { var scope = this, get = compileToFn(watchExp, 'watch'), array = scope.$$watchers, @@ -258,7 +269,8 @@ function $RootScopeProvider(){ fn: listener, last: initWatchVal, get: get, - exp: watchExp + exp: watchExp, + eq: !!objectEquality }; // in the case user pass string, we need to compile it, do we really need this ? @@ -332,7 +344,7 @@ function $RootScopeProvider(){ watchers, asyncQueue, length, - dirty, ttl = 100, + dirty, ttl = TTL, next, current, target = this, watchLog = [], logIdx, logMsg; @@ -359,9 +371,13 @@ function $RootScopeProvider(){ 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)) { + if ((value = watch.get(current)) !== (last = watch.last) && + !(watch.eq + ? equals(value, last) + : (typeof value == 'number' && typeof last == 'number' + && isNaN(value) && isNaN(last)))) { dirty = true; - watch.last = copy(value); + watch.last = watch.eq ? copy(value) : value; watch.fn(value, ((last === initWatchVal) ? value : last), current); if (ttl < 5) { logIdx = 4 - ttl; @@ -390,7 +406,7 @@ function $RootScopeProvider(){ } while ((current = next)); if(dirty && !(ttl--)) { - throw Error('100 $digest() iterations reached. Aborting!\n' + + throw Error(TTL + ' $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); } } while (dirty || asyncQueue.length); diff --git a/test/service/scopeSpec.js b/test/service/scopeSpec.js index 179ff162aeee..c4940931f4b6 100644 --- a/test/service/scopeSpec.js +++ b/test/service/scopeSpec.js @@ -189,22 +189,26 @@ describe('Scope', function() { })); - it('should prevent infinite recursion and print watcher expression',inject( - function($rootScope) { - $rootScope.$watch('a', function() {$rootScope.b++;}); - $rootScope.$watch('b', function() {$rootScope.a++;}); - $rootScope.a = $rootScope.b = 0; + it('should prevent infinite recursion and print watcher expression',function() { + module(function($rootScopeProvider) { + $rootScopeProvider.ttl(100); + }); + inject(function($rootScope) { + $rootScope.$watch('a', function() {$rootScope.b++;}); + $rootScope.$watch('b', function() {$rootScope.a++;}); + $rootScope.a = $rootScope.b = 0; - expect(function() { - $rootScope.$digest(); - }).toThrow('100 $digest() iterations reached. Aborting!\n'+ - 'Watchers fired in the last 5 iterations: ' + - '[["a; newVal: 96; oldVal: 95","b; newVal: 97; oldVal: 96"],' + - '["a; newVal: 97; oldVal: 96","b; newVal: 98; oldVal: 97"],' + - '["a; newVal: 98; oldVal: 97","b; newVal: 99; oldVal: 98"],' + - '["a; newVal: 99; oldVal: 98","b; newVal: 100; oldVal: 99"],' + - '["a; newVal: 100; oldVal: 99","b; newVal: 101; oldVal: 100"]]'); - })); + expect(function() { + $rootScope.$digest(); + }).toThrow('100 $digest() iterations reached. Aborting!\n'+ + 'Watchers fired in the last 5 iterations: ' + + '[["a; newVal: 96; oldVal: 95","b; newVal: 97; oldVal: 96"],' + + '["a; newVal: 97; oldVal: 96","b; newVal: 98; oldVal: 97"],' + + '["a; newVal: 98; oldVal: 97","b; newVal: 99; oldVal: 98"],' + + '["a; newVal: 99; oldVal: 98","b; newVal: 100; oldVal: 99"],' + + '["a; newVal: 100; oldVal: 99","b; newVal: 101; oldVal: 100"]]'); + }); + }); it('should prevent infinite recursion and print print watcher function name or body', @@ -241,11 +245,11 @@ describe('Scope', function() { $rootScope.$watch('a', function(value) { log +='.'; expect(value).toBe($rootScope.a); - }); + }, true); $rootScope.$watch('b', function(value) { log +='!'; expect(value).toBe($rootScope.b); - }); + }, true); $rootScope.$digest(); log = ''; @@ -331,7 +335,7 @@ describe('Scope', function() { $rootScope.$watch(function() { return undefined;}, logger); $rootScope.$watch(function() { return '';}, logger); $rootScope.$watch(function() { return false;}, logger); - $rootScope.$watch(function() { return {};}, logger); + $rootScope.$watch(function() { return {};}, logger, true); $rootScope.$watch(function() { return 23;}, logger); $rootScope.$digest();