From 6b91aa0a18098100e5f50ea911ee135b50680d67 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Thu, 22 Aug 2013 02:08:41 -0700 Subject: feat(Scope): async auto-flush $evalAsync queue when outside of $digest This change causes a new $digest to be scheduled in the next tick if a task was was sent to the $evalAsync queue from outside of a $digest or an $apply. While this mode of operation is not common for most of the user code, this change means that $q promises that utilze $evalAsync queue to guarantee asynchronicity of promise apis will now also resolve outside of a $digest, which turned out to be a big pain point for some developers. The implementation ensures that we don't do more work than needed and that we coalese as much work as possible into a single $digest. The use of $browser instead of setTimeout ensures that we can mock out and control the scheduling of "auto-flush", which should in theory allow all of the existing code and tests to work without negative side-effects. Closes #3539 Closes #2438 --- src/ng/rootScope.js | 23 ++++++++++++++++++----- src/ng/timeout.js | 2 +- 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'src') 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 { -- cgit v1.2.3