@@ -91,6 +91,7 @@ function $RootScopeProvider() {
9191 this . $$watchersCount = 0 ;
9292 this . $id = nextUid ( ) ;
9393 this . $$ChildScope = null ;
94+ this . $$suspended = false ;
9495 }
9596 ChildScope . prototype = parent ;
9697 return ChildScope ;
@@ -178,6 +179,7 @@ function $RootScopeProvider() {
178179 this . $$childHead = this . $$childTail = null ;
179180 this . $root = this ;
180181 this . $$destroyed = false ;
182+ this . $$suspended = false ;
181183 this . $$listeners = { } ;
182184 this . $$listenerCount = { } ;
183185 this . $$watchersCount = 0 ;
@@ -808,7 +810,7 @@ function $RootScopeProvider() {
808810
809811 traverseScopesLoop:
810812 do { // "traverse the scopes" loop
811- if ( ( watchers = current . $$watchers ) ) {
813+ if ( ( watchers = ! current . $$suspended && current . $$watchers ) ) {
812814 // process our watches
813815 watchers . $$digestWatchIndex = watchers . length ;
814816 while ( watchers . $$digestWatchIndex -- ) {
@@ -852,7 +854,9 @@ function $RootScopeProvider() {
852854 // Insanity Warning: scope depth-first traversal
853855 // yes, this code is a bit crazy, but it works and we have tests to prove it!
854856 // this piece should be kept in sync with the traversal in $broadcast
855- if ( ! ( next = ( ( current . $$watchersCount && current . $$childHead ) ||
857+ // (though it differs due to having the extra check for $$suspended and does not
858+ // check $$listenerCount)
859+ if ( ! ( next = ( ( ! current . $$suspended && current . $$watchersCount && current . $$childHead ) ||
856860 ( current !== target && current . $$nextSibling ) ) ) ) {
857861 while ( current !== target && ! ( next = current . $$nextSibling ) ) {
858862 current = current . $parent ;
@@ -889,6 +893,95 @@ function $RootScopeProvider() {
889893 $browser . $$checkUrlChange ( ) ;
890894 } ,
891895
896+ /**
897+ * @ngdoc method
898+ * @name $rootScope.Scope#$suspend
899+ * @kind function
900+ *
901+ * @description
902+ * Suspend watchers of this scope subtree so that they will not be invoked during digest.
903+ *
904+ * This can be used to optimize your application when you know that running those watchers
905+ * is redundant.
906+ *
907+ * **Warning**
908+ *
909+ * Suspending scopes from the digest cycle can have unwanted and difficult to debug results.
910+ * Only use this approach if you are confident that you know what you are doing and have
911+ * ample tests to ensure that bindings get updated as you expect.
912+ *
913+ * Some of the things to consider are:
914+ *
915+ * * Any external event on a directive/component will not trigger a digest while the hosting
916+ * scope is suspended - even if the event handler calls `$apply()` or `$rootScope.$digest()`.
917+ * * Transcluded content exists on a scope that inherits from outside a directive but exists
918+ * as a child of the directive's containing scope. If the containing scope is suspended the
919+ * transcluded scope will also be suspended, even if the scope from which the transcluded
920+ * scope inherits is not suspended.
921+ * * Multiple directives trying to manage the suspended status of a scope can confuse each other:
922+ * * A call to `$suspend()` on an already suspended scope is a no-op.
923+ * * A call to `$resume()` on a non-suspended scope is a no-op.
924+ * * If two directives suspend a scope, then one of them resumes the scope, the scope will no
925+ * longer be suspended. This could result in the other directive believing a scope to be
926+ * suspended when it is not.
927+ * * If a parent scope is suspended then all its descendants will be also excluded from future
928+ * digests whether or not they have been suspended themselves. Note that this also applies to
929+ * isolate child scopes.
930+ * * Calling `$digest()` directly on a descendant of a suspended scope will still run the watchers
931+ * for that scope and its descendants. When digesting we only check whether the current scope is
932+ * locally suspended, rather than checking whether it has a suspended ancestor.
933+ * * Calling `$resume()` on a scope that has a suspended ancestor will not cause the scope to be
934+ * included in future digests until all its ancestors have been resumed.
935+ * * Resolved promises, e.g. from explicit `$q` deferreds and `$http` calls, trigger `$apply()`
936+ * against the `$rootScope` and so will still trigger a global digest even if the promise was
937+ * initiated by a component that lives on a suspended scope.
938+ */
939+ $suspend : function ( ) {
940+ this . $$suspended = true ;
941+ } ,
942+
943+ /**
944+ * @ngdoc method
945+ * @name $rootScope.Scope#$isSuspended
946+ * @kind function
947+ *
948+ * @description
949+ * Call this method to determine if this scope has been explicitly suspended. It will not
950+ * tell you whether an ancestor has been suspended.
951+ * To determine if this scope will be excluded from a digest triggered at the $rootScope,
952+ * for example, you must check all its ancestors:
953+ *
954+ * ```
955+ * function isExcludedFromDigest(scope) {
956+ * while(scope) {
957+ * if (scope.$isSuspended()) return true;
958+ * scope = scope.$parent;
959+ * }
960+ * return false;
961+ * ```
962+ *
963+ * Be aware that a scope may not be included in digests if it has a suspended ancestor,
964+ * even if `$isSuspended()` returns false.
965+ *
966+ * @returns true if the current scope has been suspended.
967+ */
968+ $isSuspended : function ( ) {
969+ return this . $$suspended ;
970+ } ,
971+
972+ /**
973+ * @ngdoc method
974+ * @name $rootScope.Scope#$resume
975+ * @kind function
976+ *
977+ * @description
978+ * Resume watchers of this scope subtree in case it was suspended.
979+ *
980+ * See {@link $rootScope.Scope#$suspend} for information about the dangers of using this approach.
981+ */
982+ $resume : function ( ) {
983+ this . $$suspended = false ;
984+ } ,
892985
893986 /**
894987 * @ngdoc event
@@ -1289,7 +1382,8 @@ function $RootScopeProvider() {
12891382 // Insanity Warning: scope depth-first traversal
12901383 // yes, this code is a bit crazy, but it works and we have tests to prove it!
12911384 // this piece should be kept in sync with the traversal in $digest
1292- // (though it differs due to having the extra check for $$listenerCount)
1385+ // (though it differs due to having the extra check for $$listenerCount and
1386+ // does not check $$suspended)
12931387 if ( ! ( next = ( ( current . $$listenerCount [ name ] && current . $$childHead ) ||
12941388 ( current !== target && current . $$nextSibling ) ) ) ) {
12951389 while ( current !== target && ! ( next = current . $$nextSibling ) ) {
0 commit comments