@@ -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 ;
@@ -811,7 +813,7 @@ function $RootScopeProvider() {
811813
812814 traverseScopesLoop:
813815 do { // "traverse the scopes" loop
814- if ( ( watchers = current . $$watchers ) ) {
816+ if ( ( watchers = ! current . $$suspended && current . $$watchers ) ) {
815817 // process our watches
816818 watchers . $$digestWatchIndex = watchers . length ;
817819 while ( watchers . $$digestWatchIndex -- ) {
@@ -855,7 +857,7 @@ function $RootScopeProvider() {
855857 // Insanity Warning: scope depth-first traversal
856858 // yes, this code is a bit crazy, but it works and we have tests to prove it!
857859 // this piece should be kept in sync with the traversal in $broadcast
858- if ( ! ( next = ( ( current . $$watchersCount && current . $$childHead ) ||
860+ if ( ! ( next = ( ( ! current . $$suspended && current . $$watchersCount && current . $$childHead ) ||
859861 ( current !== target && current . $$nextSibling ) ) ) ) {
860862 while ( current !== target && ! ( next = current . $$nextSibling ) ) {
861863 current = current . $parent ;
@@ -892,6 +894,95 @@ function $RootScopeProvider() {
892894 $browser . $$checkUrlChange ( ) ;
893895 } ,
894896
897+ /**
898+ * @ngdoc method
899+ * @name $rootScope.Scope#$suspend
900+ * @kind function
901+ *
902+ * @description
903+ * Suspend watchers of this scope subtree so that they will not be invoked during digest.
904+ *
905+ * This can be used to optimize your application when you know that running those watchers
906+ * is redundant.
907+ *
908+ * **Warning**
909+ *
910+ * Suspending scopes from the digest cycle can have unwanted and difficult to debug results.
911+ * Only use this approach if you are confident that you know what you are doing and have
912+ * ample tests to ensure that bindings get updated as you expect.
913+ *
914+ * Some of the things to consider are:
915+ *
916+ * * Any external event on a directive/component will not trigger a digest while the hosting
917+ * scope is suspended - even if the event handler calls `$apply()` or `$digest()`.
918+ * * Transcluded content exists on a scope that inherits from outside a directive but exists
919+ * as a child of the directive's containing scope. If the containing scope is suspended the
920+ * transcluded scope will also be suspended, even if the scope from which the transcluded
921+ * scope inherits is not suspended.
922+ * * Multiple directives trying to manage the suspended status of a scope can confuse each other:
923+ * * A call to `$suspend` an already suspended scope is a no-op.
924+ * * A call to `$resume` a non-suspended scope is a no-op.
925+ * * If two directives suspend a scope, then one of them resumes the scope, the scope will no
926+ * longer be suspended. This could result in the other directive believing a scope to be
927+ * suspended when it is not.
928+ * * If a parent scope is suspended then all its descendants will be also excluded from future
929+ * digests whether or not they have been suspended themselves. Note that this also applies to
930+ * isolate child scopes.
931+ * * Calling `$digest()` directly on a descendant of a suspended scope will still run the watchers
932+ * for that scope and its descendants. When digesting we only check whether the current scope is
933+ * locally suspended, rather than checking whether it has a suspended ancestor.
934+ * * Calling `$resume()` on a scope that has a suspended ancestor will not cause the scope to be
935+ * included in future digests until all its ancestors have been resumed.
936+ * * Resolved promises, e.g. from explicit `$q` deferreds and `$http` calls, trigger `$apply()`
937+ * against the `$rootScope` and so will still trigger a global digest even if the promise was
938+ * initialiated by a component that lives on a suspended scope.
939+ */
940+ $suspend : function ( ) {
941+ this . $$suspended = true ;
942+ } ,
943+
944+ /**
945+ * @ngdoc method
946+ * @name $rootScope.Scope#$isSuspended
947+ * @kind function
948+ *
949+ * @description
950+ * Call this method to determine if this scope has been explicitly suspended. It will not
951+ * tell you whether an ancestor has been suspended.
952+ * To determine if this scope will be excluded from a digest triggered at the $rootScope,
953+ * for example, you must check all its ancestors:
954+ *
955+ * ```
956+ * function isExcludedFromDigest(scope) {
957+ * while(scope) {
958+ * if (scope.$isSuspended()) return true;
959+ * scope = scope.$parent;
960+ * }
961+ * return false;
962+ * ```
963+ *
964+ * Be aware that a scope may not be included in digests if it has a suspended ancestor,
965+ * even if `$isSuspended()` returns false.
966+ *
967+ * @returns true if the current scope has been suspended.
968+ */
969+ $isSuspended : function ( ) {
970+ return this . $$suspended ;
971+ } ,
972+
973+ /**
974+ * @ngdoc method
975+ * @name $rootScope.Scope#$resume
976+ * @kind function
977+ *
978+ * @description
979+ * Resume watchers of this scope subtree in case it was suspended.
980+ *
981+ * See {@link $rootScope.Scope#$suspend} for information about the dangers of using this approach.
982+ */
983+ $resume : function ( ) {
984+ this . $$suspended = false ;
985+ } ,
895986
896987 /**
897988 * @ngdoc event
0 commit comments