diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index 18c544340c44..d3b2762d8c01 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -119,6 +119,7 @@ function $RootScopeProvider(){ this['this'] = this.$root = this; this.$$destroyed = false; this.$$asyncQueue = []; + this.$$postDigestQueue = []; this.$$listeners = {}; this.$$isolateBindings = {}; } @@ -133,6 +134,7 @@ function $RootScopeProvider(){ Scope.prototype = { + constructor: Scope, /** * @ngdoc function * @name ng.$rootScope.Scope#$new @@ -167,6 +169,7 @@ function $RootScopeProvider(){ child.$root = this.$root; // ensure that there is just one async queue per $rootScope and it's children child.$$asyncQueue = this.$$asyncQueue; + child.$$postDigestQueue = this.$$postDigestQueue; } 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 @@ -494,6 +497,7 @@ function $RootScopeProvider(){ var watch, value, last, watchers, asyncQueue = this.$$asyncQueue, + postDigestQueue = this.$$postDigestQueue, length, dirty, ttl = TTL, next, current, target = this, @@ -566,6 +570,14 @@ function $RootScopeProvider(){ } while (dirty || asyncQueue.length); clearPhase(); + + while(postDigestQueue.length) { + try { + postDigestQueue.shift()(); + } catch (e) { + $exceptionHandler(e); + } + } }, @@ -696,6 +708,10 @@ function $RootScopeProvider(){ this.$$asyncQueue.push(expr); }, + $$postDigest : function(expr) { + this.$$postDigestQueue.push(expr); + }, + /** * @ngdoc function * @name ng.$rootScope.Scope#$apply diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index d6a684a79a3d..656385e9681c 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -12,6 +12,12 @@ describe('Scope', function() { })); + it('should expose the constructor', inject(function($rootScope) { + if (msie) return; + expect($rootScope.__proto__).toBe($rootScope.constructor.prototype); + })); + + it('should not have $root on children, but should inherit', inject(function($rootScope) { var child = $rootScope.$new(); expect(child.$root).toEqual($rootScope); @@ -672,6 +678,74 @@ describe('Scope', function() { expect(log).toEqual('parent.async;child.async;parent.$digest;child.$digest;'); })); + it('should not run another digest for an $$postDigest call', inject(function($rootScope) { + var internalWatchCount = 0; + var externalWatchCount = 0; + + $rootScope.internalCount = 0; + $rootScope.externalCount = 0; + + $rootScope.$evalAsync(function(scope) { + $rootScope.internalCount++; + }); + + $rootScope.$$postDigest(function(scope) { + $rootScope.externalCount++; + }); + + $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 a $$postDigest call on all child scopes when a parent scope is digested', inject(function($rootScope) { + var parent = $rootScope.$new(), + child = parent.$new(), + count = 0; + + $rootScope.$$postDigest(function() { + count++; + }); + + parent.$$postDigest(function() { + count++; + }); + + child.$$postDigest(function() { + count++; + }); + + expect(count).toBe(0); + $rootScope.$digest(); + expect(count).toBe(3); + })); + + it('should run a $$postDigest call even if the child scope is isolated', inject(function($rootScope) { + var parent = $rootScope.$new(), + child = parent.$new(true), + signature = ''; + + parent.$$postDigest(function() { + signature += 'A'; + }); + + child.$$postDigest(function() { + signature += 'B'; + }); + + expect(signature).toBe(''); + $rootScope.$digest(); + expect(signature).toBe('AB'); + })); + it('should cause a $digest rerun', inject(function($rootScope) { $rootScope.log = ''; $rootScope.value = 0;