@@ -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 ;
@@ -832,7 +834,7 @@ function $RootScopeProvider() {
832834
833835 traverseScopesLoop:
834836 do { // "traverse the scopes" loop
835- if ( ( watchers = current . $$watchers ) ) {
837+ if ( ( watchers = ! current . $$suspended && current . $$watchers ) ) {
836838 // process our watches
837839 watchers . $$digestWatchIndex = watchers . length ;
838840 while ( watchers . $$digestWatchIndex -- ) {
@@ -876,7 +878,9 @@ function $RootScopeProvider() {
876878 // Insanity Warning: scope depth-first traversal
877879 // yes, this code is a bit crazy, but it works and we have tests to prove it!
878880 // this piece should be kept in sync with the traversal in $broadcast
879- if ( ! ( next = ( ( current . $$watchersCount && current . $$childHead ) ||
881+ // (though it differs due to having the extra check for $$suspended and does not
882+ // check $$listenerCount)
883+ if ( ! ( next = ( ( ! current . $$suspended && current . $$watchersCount && current . $$childHead ) ||
880884 ( current !== target && current . $$nextSibling ) ) ) ) {
881885 while ( current !== target && ! ( next = current . $$nextSibling ) ) {
882886 current = current . $parent ;
@@ -913,6 +917,95 @@ function $RootScopeProvider() {
913917 $browser . $$checkUrlChange ( ) ;
914918 } ,
915919
920+ /**
921+ * @ngdoc method
922+ * @name $rootScope.Scope#$suspend
923+ * @kind function
924+ *
925+ * @description
926+ * Suspend watchers of this scope subtree so that they will not be invoked during digest.
927+ *
928+ * This can be used to optimize your application when you know that running those watchers
929+ * is redundant.
930+ *
931+ * **Warning**
932+ *
933+ * Suspending scopes from the digest cycle can have unwanted and difficult to debug results.
934+ * Only use this approach if you are confident that you know what you are doing and have
935+ * ample tests to ensure that bindings get updated as you expect.
936+ *
937+ * Some of the things to consider are:
938+ *
939+ * * Any external event on a directive/component will not trigger a digest while the hosting
940+ * scope is suspended - even if the event handler calls `$apply()` or `$rootScope.$digest()`.
941+ * * Transcluded content exists on a scope that inherits from outside a directive but exists
942+ * as a child of the directive's containing scope. If the containing scope is suspended the
943+ * transcluded scope will also be suspended, even if the scope from which the transcluded
944+ * scope inherits is not suspended.
945+ * * Multiple directives trying to manage the suspended status of a scope can confuse each other:
946+ * * A call to `$suspend()` on an already suspended scope is a no-op.
947+ * * A call to `$resume()` on a non-suspended scope is a no-op.
948+ * * If two directives suspend a scope, then one of them resumes the scope, the scope will no
949+ * longer be suspended. This could result in the other directive believing a scope to be
950+ * suspended when it is not.
951+ * * If a parent scope is suspended then all its descendants will be also excluded from future
952+ * digests whether or not they have been suspended themselves. Note that this also applies to
953+ * isolate child scopes.
954+ * * Calling `$digest()` directly on a descendant of a suspended scope will still run the watchers
955+ * for that scope and its descendants. When digesting we only check whether the current scope is
956+ * locally suspended, rather than checking whether it has a suspended ancestor.
957+ * * Calling `$resume()` on a scope that has a suspended ancestor will not cause the scope to be
958+ * included in future digests until all its ancestors have been resumed.
959+ * * Resolved promises, e.g. from explicit `$q` deferreds and `$http` calls, trigger `$apply()`
960+ * against the `$rootScope` and so will still trigger a global digest even if the promise was
961+ * initiated by a component that lives on a suspended scope.
962+ */
963+ $suspend : function ( ) {
964+ this . $$suspended = true ;
965+ } ,
966+
967+ /**
968+ * @ngdoc method
969+ * @name $rootScope.Scope#$isSuspended
970+ * @kind function
971+ *
972+ * @description
973+ * Call this method to determine if this scope has been explicitly suspended. It will not
974+ * tell you whether an ancestor has been suspended.
975+ * To determine if this scope will be excluded from a digest triggered at the $rootScope,
976+ * for example, you must check all its ancestors:
977+ *
978+ * ```
979+ * function isExcludedFromDigest(scope) {
980+ * while(scope) {
981+ * if (scope.$isSuspended()) return true;
982+ * scope = scope.$parent;
983+ * }
984+ * return false;
985+ * ```
986+ *
987+ * Be aware that a scope may not be included in digests if it has a suspended ancestor,
988+ * even if `$isSuspended()` returns false.
989+ *
990+ * @returns true if the current scope has been suspended.
991+ */
992+ $isSuspended : function ( ) {
993+ return this . $$suspended ;
994+ } ,
995+
996+ /**
997+ * @ngdoc method
998+ * @name $rootScope.Scope#$resume
999+ * @kind function
1000+ *
1001+ * @description
1002+ * Resume watchers of this scope subtree in case it was suspended.
1003+ *
1004+ * See {@link $rootScope.Scope#$suspend} for information about the dangers of using this approach.
1005+ */
1006+ $resume : function ( ) {
1007+ this . $$suspended = false ;
1008+ } ,
9161009
9171010 /**
9181011 * @ngdoc event
@@ -1348,7 +1441,8 @@ function $RootScopeProvider() {
13481441 // Insanity Warning: scope depth-first traversal
13491442 // yes, this code is a bit crazy, but it works and we have tests to prove it!
13501443 // this piece should be kept in sync with the traversal in $digest
1351- // (though it differs due to having the extra check for $$listenerCount)
1444+ // (though it differs due to having the extra check for $$listenerCount and
1445+ // does not check $$suspended)
13521446 if ( ! ( next = ( ( current . $$listenerCount [ name ] && current . $$childHead ) ||
13531447 ( current !== target && current . $$nextSibling ) ) ) ) {
13541448 while ( current !== target && ! ( next = current . $$nextSibling ) ) {
0 commit comments