Skip to content
This repository was archived by the owner on Jul 5, 2023. It is now read-only.

Commit 1f258a4

Browse files
committed
Fix bug caused when invoking $digest directly on a scope
rather than on root scope. Any child scope of sly-prevent-evaluation-when-hidden would return incorrect results.
1 parent 95c8819 commit 1f258a4

File tree

3 files changed

+104
-2
lines changed

3 files changed

+104
-2
lines changed

scalyr.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ defineScalyrAngularModule('gatedScope', [])
747747
// We just have to do the work that normally a child class's
748748
// constructor would perform -- initializing our instance vars.
749749
result.$$gatingFunction = this.$$gatingFunction;
750+
result.$$parentGatingFunction = this.$$gatingFunction;
750751
result.$$shouldGateFunction = this.$$shouldGateFunction;
751752
result.$$gatedWatchers = [];
752753

@@ -848,7 +849,39 @@ defineScalyrAngularModule('gatedScope', [])
848849
return scopePrototype.$watch.call(this, watchExpression, listener, objectEquality);
849850
}
850851
};
851-
852+
853+
/**
854+
* @inherited $digest
855+
*/
856+
methodsToAdd.$digest = function gatedDigest() {
857+
// We have to take care if a scope's digest method was invoked that has a
858+
// gating function in the parent scope. In this case, the watcher for that
859+
// gating function is registered in the parent (the one added in gatedWatch),
860+
// and will not be evaluated here. So, we have to manually see if the gating
861+
// function is true and if so, evaluate any gated watchers for that function on
862+
// this scope. This needs to happen to properly support invoking $digest on a
863+
// scope with a parent scope with a gating function.
864+
// NOTE: It is arguable that we are not correctly handling nested gating functions
865+
// here since we do not know if the parent gating function was nested in other gating
866+
// functions and should be evaluated at all. However, if a caller is invoking
867+
// $digest on a particular scope, we assume the caller is doing that because it
868+
// knows the watchers should be evaluated.
869+
if (!isNull(this.$$parentGatingFunction) && this.$$parentGatingFunction()) {
870+
var dirty = false;
871+
var ttl = 5;
872+
do {
873+
dirty = this.$digestGated(this.$$parentGatingFunction);
874+
ttl--;
875+
876+
if (dirty && !(ttl--)) {
877+
throw Error(TTL + ' $digest() iterations reached for gated watcher. Aborting!\n' +
878+
'Watchers fired in the last 5 iterations.');
879+
}
880+
} while (dirty);
881+
}
882+
return scopePrototype.$digest.call(this) || dirty;
883+
}
884+
852885
/**
853886
* Modifies this scope so that all future watchers registered by $watch will
854887
* only be evaluated if gatingFunction returns true. Optionally, you may specify
@@ -916,6 +949,7 @@ defineScalyrAngularModule('gatedScope', [])
916949
angular.extend($rootScope, methodsToAdd);
917950

918951
$rootScope.$$gatingFunction = null;
952+
$rootScope.$$parentGatingFunction = null;
919953
$rootScope.$$shouldGateFunction = null;
920954
$rootScope.$$gatedWatchers = [];
921955

src/js/lib/gatedScope.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ defineScalyrAngularModule('gatedScope', [])
4949
// We just have to do the work that normally a child class's
5050
// constructor would perform -- initializing our instance vars.
5151
result.$$gatingFunction = this.$$gatingFunction;
52+
result.$$parentGatingFunction = this.$$gatingFunction;
5253
result.$$shouldGateFunction = this.$$shouldGateFunction;
5354
result.$$gatedWatchers = [];
5455

@@ -150,7 +151,39 @@ defineScalyrAngularModule('gatedScope', [])
150151
return scopePrototype.$watch.call(this, watchExpression, listener, objectEquality);
151152
}
152153
};
153-
154+
155+
/**
156+
* @inherited $digest
157+
*/
158+
methodsToAdd.$digest = function gatedDigest() {
159+
// We have to take care if a scope's digest method was invoked that has a
160+
// gating function in the parent scope. In this case, the watcher for that
161+
// gating function is registered in the parent (the one added in gatedWatch),
162+
// and will not be evaluated here. So, we have to manually see if the gating
163+
// function is true and if so, evaluate any gated watchers for that function on
164+
// this scope. This needs to happen to properly support invoking $digest on a
165+
// scope with a parent scope with a gating function.
166+
// NOTE: It is arguable that we are not correctly handling nested gating functions
167+
// here since we do not know if the parent gating function was nested in other gating
168+
// functions and should be evaluated at all. However, if a caller is invoking
169+
// $digest on a particular scope, we assume the caller is doing that because it
170+
// knows the watchers should be evaluated.
171+
if (!isNull(this.$$parentGatingFunction) && this.$$parentGatingFunction()) {
172+
var dirty = false;
173+
var ttl = 5;
174+
do {
175+
dirty = this.$digestGated(this.$$parentGatingFunction);
176+
ttl--;
177+
178+
if (dirty && !(ttl--)) {
179+
throw Error(TTL + ' $digest() iterations reached for gated watcher. Aborting!\n' +
180+
'Watchers fired in the last 5 iterations.');
181+
}
182+
} while (dirty);
183+
}
184+
return scopePrototype.$digest.call(this) || dirty;
185+
}
186+
154187
/**
155188
* Modifies this scope so that all future watchers registered by $watch will
156189
* only be evaluated if gatingFunction returns true. Optionally, you may specify
@@ -218,6 +251,7 @@ defineScalyrAngularModule('gatedScope', [])
218251
angular.extend($rootScope, methodsToAdd);
219252

220253
$rootScope.$$gatingFunction = null;
254+
$rootScope.$$parentGatingFunction = null;
221255
$rootScope.$$shouldGateFunction = null;
222256
$rootScope.$$gatedWatchers = [];
223257

src/tests/lib/gatedScopeTest.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,40 @@ describe('GatedScope', function() {
122122
expect(counter).toEqual(6);
123123
});
124124

125+
it('should only evaluate gated watches on scope whose digest was invoked', function() {
126+
var gateClosed = false;
127+
128+
var childA = $rootScope.$new();
129+
childA.$addWatcherGate(function() {
130+
return !gateClosed;
131+
});
132+
133+
var childB = childA.$new();
134+
135+
var counter = 0;
136+
var watchedVal = 1;
137+
function watcher() {
138+
++counter;
139+
return watchedVal;
140+
}
141+
142+
childA.$watch(watcher);
143+
childB.$watch(watcher);
144+
145+
childB.$digest();
146+
147+
// Only the watcher on B should fire since we invoked its digest.
148+
expect(counter).toEqual(2);
149+
150+
counter = 0;
151+
watchedValue = 2;
152+
153+
childA.$digest();
154+
// Both watches should evaluate twice (twice since the first time
155+
// will indicate it is is dirty).
156+
expect(counter).toEqual(4);
157+
});
158+
125159
it('should only evaluate gated watchers for gating function that returned true', function() {
126160
var counter1 = 0;
127161
function watcher1() {

0 commit comments

Comments
 (0)