From be945a4566ea5e8702ef3362ab8ef2e3a0952cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 13 Aug 2013 20:51:03 -0400 Subject: [PATCH 1/2] feat($rootScope): allow $evalSync to have issue operations to be run external to digests internal/external digests --- src/ng/rootScope.js | 28 ++++++++++++++------ test/ng/rootScopeSpec.js | 56 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index d94a621d94c6..94f822cd4074 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -118,7 +118,8 @@ function $RootScopeProvider(){ this.$$childHead = this.$$childTail = null; this['this'] = this.$root = this; this.$$destroyed = false; - this.$$asyncQueue = []; + this.$$internalAsyncQueue = []; + this.$$externalAsyncQueue = []; this.$$listeners = {}; this.$$isolateBindings = {}; } @@ -166,7 +167,8 @@ function $RootScopeProvider(){ child = new Scope(); child.$root = this.$root; // ensure that there is just one async queue per $rootScope and it's children - child.$$asyncQueue = this.$$asyncQueue; + child.$$internalAsyncQueue = this.$$internalAsyncQueue; + child.$$externalAsyncQueue = this.$$externalAsyncQueue; } else { 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 @@ -493,7 +495,8 @@ function $RootScopeProvider(){ $digest: function() { var watch, value, last, watchers, - asyncQueue = this.$$asyncQueue, + internalAsyncQueue = this.$$internalAsyncQueue, + externalAsyncQueue = this.$$externalAsyncQueue, length, dirty, ttl = TTL, next, current, target = this, @@ -506,9 +509,9 @@ function $RootScopeProvider(){ dirty = false; current = target; - while(asyncQueue.length) { + while(internalAsyncQueue.length) { try { - current.$eval(asyncQueue.shift()); + current.$eval(internalAsyncQueue.shift()); } catch (e) { $exceptionHandler(e); } @@ -563,9 +566,17 @@ function $RootScopeProvider(){ '{0} $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: {1}', TTL, toJson(watchLog)); } - } while (dirty || asyncQueue.length); + } while (dirty || internalAsyncQueue.length); clearPhase(); + + while(externalAsyncQueue.length) { + try { + target.$eval(externalAsyncQueue.shift()); + } catch (e) { + $exceptionHandler(e); + } + } }, @@ -678,9 +689,10 @@ function $RootScopeProvider(){ * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * + * @param {boolean} runDigest Whether or not to skip the next digest cycle after the expr is evaluated */ - $evalAsync: function(expr) { - this.$$asyncQueue.push(expr); + $evalAsync: function(expr, runDigest) { + (runDigest === false ? this.$$externalAsyncQueue : this.$$internalAsyncQueue).push(expr); }, /** diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index ddd830881d9b..469f8b771066 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -672,6 +672,56 @@ describe('Scope', function() { expect(log).toEqual('parent.async;child.async;parent.$digest;child.$digest;'); })); + it('should not run another digest for an $evalAsync call when it is external', inject(function($rootScope) { + var internalWatchCount = 0; + var externalWatchCount = 0; + + $rootScope.internalCount = 0; + $rootScope.externalCount = 0; + + $rootScope.$evalAsync(function(scope) { + $rootScope.internalCount++; + }); + + $rootScope.$evalAsync(function(scope) { + $rootScope.externalCount++; + }, false); + + $rootScope.$watch('internalCount', function(value) { + internalWatchCount = value; + }); + $rootScope.$watch('externalCount', function(value) { + externalWatchCount = value; + }); + + $rootScope.$digest(); + + expect(internalWatchCount).toEqual(1); + expect(externalWatchCount).toEqual(0); + })); + + it('should run an external evalAsync call on all child scopes when a parent scope is digested', inject(function($rootScope) { + var parent = $rootScope.$new(), + child = parent.$new(), + count = 0; + + $rootScope.$evalAsync(function() { + count++; + }, false); + + parent.$evalAsync(function() { + count++; + }, false); + + child.$evalAsync(function() { + count++; + }, false); + + expect(count).toBe(0); + $rootScope.$digest(); + expect(count).toBe(3); + })); + it('should cause a $digest rerun', inject(function($rootScope) { $rootScope.log = ''; $rootScope.value = 0; @@ -701,9 +751,9 @@ describe('Scope', function() { childScope.$evalAsync('childExpression'); isolateScope.$evalAsync('isolateExpression'); - expect(childScope.$$asyncQueue).toBe($rootScope.$$asyncQueue); - expect(isolateScope.$$asyncQueue).toBe($rootScope.$$asyncQueue); - expect($rootScope.$$asyncQueue).toEqual(['rootExpression', 'childExpression', 'isolateExpression']); + expect(childScope.$$internalAsyncQueue).toBe($rootScope.$$internalAsyncQueue); + expect(isolateScope.$$internalAsyncQueue).toBe($rootScope.$$internalAsyncQueue); + expect($rootScope.$$internalAsyncQueue).toEqual(['rootExpression', 'childExpression', 'isolateExpression']); })); }); From f9efdee38bc4a866eb26501939f468a4b0cb60c2 Mon Sep 17 00:00:00 2001 From: jankuca Date: Fri, 16 Aug 2013 15:41:40 -0700 Subject: [PATCH 2/2] chore(rootScope): add test showing $apply->$evalAsync(fn, false) usage --- test/ng/rootScopeSpec.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index 469f8b771066..15611d652936 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -755,6 +755,26 @@ describe('Scope', function() { expect(isolateScope.$$internalAsyncQueue).toBe($rootScope.$$internalAsyncQueue); expect($rootScope.$$internalAsyncQueue).toEqual(['rootExpression', 'childExpression', 'isolateExpression']); })); + + it('should include $evalAsync(fn, false) callbacks added during $apply() in the queue dispatched after the DOM is updated', function () { + inject(function ($compile, $rootScope) { + var element = $compile( + '
{{item}}
')($rootScope); + + var executed = false; + $rootScope.items = [ 'a;', 'b;' ]; + $rootScope.$apply(function () { + $rootScope.$evalAsync(function () { + executed = true; + expect(element.text()).toBe('a;b;'); + }, false); + }); + + expect(executed).toBe(true); + + dealoc(element); + }); + }); });