@@ -178,6 +178,7 @@ function $RootScopeProvider() {
178178 this . $$childHead = this . $$childTail = null ;
179179 this . $root = this ;
180180 this . $$destroyed = false ;
181+ this . $$suspended = false ;
181182 this . $$listeners = { } ;
182183 this . $$listenerCount = { } ;
183184 this . $$watchersCount = 0 ;
@@ -811,7 +812,7 @@ function $RootScopeProvider() {
811812
812813 traverseScopesLoop:
813814 do { // "traverse the scopes" loop
814- if ( ( watchers = current . $$watchers ) ) {
815+ if ( ( watchers = ! current . $$suspended && current . $$watchers ) ) {
815816 // process our watches
816817 watchers . $$digestWatchIndex = watchers . length ;
817818 while ( watchers . $$digestWatchIndex -- ) {
@@ -855,7 +856,7 @@ function $RootScopeProvider() {
855856 // Insanity Warning: scope depth-first traversal
856857 // yes, this code is a bit crazy, but it works and we have tests to prove it!
857858 // this piece should be kept in sync with the traversal in $broadcast
858- if ( ! ( next = ( ( current . $$watchersCount && current . $$childHead ) ||
859+ if ( ! ( next = ( ( ! current . $$suspended && current . $$watchersCount && current . $$childHead ) ||
859860 ( current !== target && current . $$nextSibling ) ) ) ) {
860861 while ( current !== target && ! ( next = current . $$nextSibling ) ) {
861862 current = current . $parent ;
@@ -892,6 +893,94 @@ function $RootScopeProvider() {
892893 $browser . $$checkUrlChange ( ) ;
893894 } ,
894895
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 `$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` an already suspended scope is a no-op.
923+ * * A call to `$resume` 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 excluded from future digests
928+ * whether or not they have been suspended themselves. Note that this also applies to isolate
929+ * child scopes.
930+ * * Calling `$resume()` on a scope that has a suspended ancestor will not cause the scope to be
931+ * included in future digests until all its ancestors have been resumed.
932+ * * Resolved promises, e.g. from explicit `$q` deferreds and `$http` calls, trigger `$apply`
933+ * against the `$rootScope` and so will still trigger a global digest even if the promise was
934+ * initialized by a component that lives on a suspended scope.
935+ */
936+ $suspend : function ( ) {
937+ this . $$suspended = true ;
938+ } ,
939+
940+ /**
941+ * @ngdoc method
942+ * @name $rootScope.Scope#$isSuspended
943+ * @kind function
944+ *
945+ * @description
946+ * Call this method to determine if this scope has been suspended.
947+ *
948+ * Be aware that a scope may not be included in digests if it has a suspended ancestor,
949+ * even if `$isSuspended()` returns true.
950+ *
951+ * To determine if this scope will be excluded in digests then you must check all its ancestors:
952+ *
953+ * ```
954+ * function isExcludedFromDigest(scope) {
955+ * while(scope) {
956+ * if (scope.$isSuspended()) return true;
957+ * scope = scope.$parent;
958+ * }
959+ * return false;
960+ * ```
961+ *
962+ * @returns true if the current scope has been suspended.
963+ */
964+ $isSuspended : function ( ) {
965+ return this . $$suspended ;
966+ } ,
967+
968+ /**
969+ * @ngdoc method
970+ * @name $rootScope.Scope#$resume
971+ * @kind function
972+ *
973+ * @description
974+ * Resume watchers of this scope subtree in case it was suspended.
975+ *
976+ * It is recommended to digest on this scope after it is resumed to catch any modifications
977+ * that might have happened while it was suspended.
978+ *
979+ * See {@link $rootScope.Scope#$suspend} for information about the dangers of using this approach.
980+ */
981+ $resume : function ( ) {
982+ delete this . $$suspended ;
983+ } ,
895984
896985 /**
897986 * @ngdoc event
0 commit comments