diff options
| -rw-r--r-- | src/ng/rootScope.js | 23 | ||||
| -rw-r--r-- | src/ng/timeout.js | 2 | ||||
| -rw-r--r-- | test/ng/rootScopeSpec.js | 51 | 
3 files changed, 70 insertions, 6 deletions
diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index d94a621d..18c54434 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -69,8 +69,8 @@ function $RootScopeProvider(){      return TTL;    }; -  this.$get = ['$injector', '$exceptionHandler', '$parse', -      function( $injector,   $exceptionHandler,   $parse) { +  this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', +      function( $injector,   $exceptionHandler,   $parse,   $browser) {      /**       * @ngdoc function @@ -666,13 +666,16 @@ function $RootScopeProvider(){         *         * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:         * -       *   - it will execute in the current script execution context (before any DOM rendering). -       *   - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after -       *     `expression` execution. +       *   - it will execute after the function that schedule the evaluation is done running (preferably before DOM rendering). +       *   - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after `expression` execution.         *         * Any exceptions from the execution of the expression are forwarded to the         * {@link ng.$exceptionHandler $exceptionHandler} service.         * +       * __Note:__ if this function is called outside of `$digest` cycle, a new $digest cycle will be scheduled. +       * It is however encouraged to always call code that changes the model from withing an `$apply` call. +       * That includes code evaluated via `$evalAsync`. +       *         * @param {(string|function())=} expression An angular expression to be executed.         *         *    - `string`: execute using the rules as defined in  {@link guide/expression expression}. @@ -680,6 +683,16 @@ function $RootScopeProvider(){         *         */        $evalAsync: function(expr) { +        // if we are outside of an $digest loop and this is the first time we are scheduling async task also schedule +        // async auto-flush +        if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { +          $browser.defer(function() { +            if ($rootScope.$$asyncQueue.length) { +              $rootScope.$digest(); +            } +          }); +        } +          this.$$asyncQueue.push(expr);        }, diff --git a/src/ng/timeout.js b/src/ng/timeout.js index 6cb62d7a..a32538ee 100644 --- a/src/ng/timeout.js +++ b/src/ng/timeout.js @@ -36,7 +36,7 @@ function $TimeoutProvider() {        var deferred = $q.defer(),            promise = deferred.promise,            skipApply = (isDefined(invokeApply) && !invokeApply), -          timeoutId, cleanup; +          timeoutId;        timeoutId = $browser.defer(function() {          try { diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index ddd83088..0a85d9a8 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -705,6 +705,57 @@ describe('Scope', function() {        expect(isolateScope.$$asyncQueue).toBe($rootScope.$$asyncQueue);        expect($rootScope.$$asyncQueue).toEqual(['rootExpression', 'childExpression', 'isolateExpression']);      })); + + +    describe('auto-flushing when queueing outside of an $apply', function() { +      var log, $rootScope, $browser; + +      beforeEach(inject(function(_log_, _$rootScope_, _$browser_) { +        log = _log_; +        $rootScope = _$rootScope_; +        $browser = _$browser_; +      })); + + +      it('should auto-flush the queue asynchronously and trigger digest', function() { +        $rootScope.$evalAsync(log.fn('eval-ed!')); +        $rootScope.$watch(log.fn('digesting')); +        expect(log).toEqual([]); + +        $browser.defer.flush(0); + +        expect(log).toEqual(['eval-ed!', 'digesting', 'digesting']); +      }); + + +      it('should not trigger digest asynchronously if the queue is empty in the next tick', function() { +        $rootScope.$evalAsync(log.fn('eval-ed!')); +        $rootScope.$watch(log.fn('digesting')); +        expect(log).toEqual([]); + +        $rootScope.$digest(); + +        expect(log).toEqual(['eval-ed!', 'digesting', 'digesting']); +        log.reset(); + +        $browser.defer.flush(0); + +        expect(log).toEqual([]); +      }); + + +      it('should not schedule more than one auto-flush task', function() { +        $rootScope.$evalAsync(log.fn('eval-ed 1!')); +        $rootScope.$evalAsync(log.fn('eval-ed 2!')); + +        $browser.defer.flush(0); +        expect(log).toEqual(['eval-ed 1!', 'eval-ed 2!']); + +        expect(function() { +          $browser.defer.flush(0); +        }).toThrow('No deferred tasks with delay up to 0ms to be flushed!'); +      }); +    });    });  | 
