Skip to content

Commit bab9fea

Browse files
committed
fix($parse): respect the interceptor.$stateful flag
1 parent 529550d commit bab9fea

File tree

2 files changed

+113
-20
lines changed

2 files changed

+113
-20
lines changed

src/ng/parse.js

+24-20
Original file line numberDiff line numberDiff line change
@@ -1798,15 +1798,9 @@ function $ParseProvider() {
17981798
var lexer = new Lexer($parseOptions);
17991799
var parser = new Parser(lexer, $filter, $parseOptions);
18001800
parsedExpression = parser.parse(exp);
1801-
if (parsedExpression.constant) {
1802-
parsedExpression.$$watchDelegate = constantWatchDelegate;
1803-
} else if (oneTime) {
1804-
parsedExpression.oneTime = true;
1805-
parsedExpression.$$watchDelegate = oneTimeWatchDelegate;
1806-
} else if (parsedExpression.inputs) {
1807-
parsedExpression.$$watchDelegate = inputsWatchDelegate;
1808-
}
1809-
cache[cacheKey] = parsedExpression;
1801+
parsedExpression.oneTime = !!oneTime;
1802+
1803+
cache[cacheKey] = addWatchDelegate(parsedExpression);
18101804
}
18111805
return addInterceptor(parsedExpression, interceptorFn);
18121806

@@ -1931,9 +1925,21 @@ function $ParseProvider() {
19311925
return unwatch;
19321926
}
19331927

1928+
function addWatchDelegate(parsedExpression) {
1929+
if (parsedExpression.constant) {
1930+
parsedExpression.$$watchDelegate = constantWatchDelegate;
1931+
} else if (parsedExpression.oneTime) {
1932+
parsedExpression.$$watchDelegate = oneTimeWatchDelegate;
1933+
} else if (parsedExpression.inputs) {
1934+
parsedExpression.$$watchDelegate = inputsWatchDelegate;
1935+
}
1936+
1937+
return parsedExpression;
1938+
}
1939+
19341940
function addInterceptor(parsedExpression, interceptorFn) {
19351941
if (!interceptorFn) return parsedExpression;
1936-
var watchDelegate = parsedExpression.$$watchDelegate;
1942+
19371943
var useInputs = false;
19381944

19391945
var isDone = parsedExpression.literal ? isAllDefined : isDefined;
@@ -1953,18 +1959,16 @@ function $ParseProvider() {
19531959

19541960
var fn = parsedExpression.oneTime ? oneTimeInterceptedExpression : regularInterceptedExpression;
19551961

1956-
// Propogate the literal/oneTime attributes
1962+
// Propogate the literal/oneTime/constant attributes
19571963
fn.literal = parsedExpression.literal;
19581964
fn.oneTime = parsedExpression.oneTime;
1965+
fn.constant = parsedExpression.constant;
19591966

1960-
// Propagate or create inputs / $$watchDelegates
1961-
useInputs = !parsedExpression.inputs;
1962-
if (watchDelegate && watchDelegate !== inputsWatchDelegate) {
1963-
fn.$$watchDelegate = watchDelegate;
1964-
fn.inputs = parsedExpression.inputs;
1965-
} else if (!interceptorFn.$stateful) {
1966-
// Treat interceptor like filters - assume non-stateful by default and use the inputsWatchDelegate
1967-
fn.$$watchDelegate = inputsWatchDelegate;
1967+
// Treat the interceptor like filters.
1968+
// If it is not $stateful then only watch its inputs.
1969+
// If the expression itself has no inputs then use the full expression as an input.
1970+
if (!interceptorFn.$stateful) {
1971+
useInputs = !parsedExpression.inputs;
19681972
fn.inputs = (parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]).map(function(e) {
19691973
// Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a
19701974
// potentially non-pure interceptor function.
@@ -1975,7 +1979,7 @@ function $ParseProvider() {
19751979
});
19761980
}
19771981

1978-
return fn;
1982+
return addWatchDelegate(fn);
19791983
}
19801984
}];
19811985
}

test/ng/parseSpec.js

+89
Original file line numberDiff line numberDiff line change
@@ -3324,6 +3324,72 @@ describe('parser', function() {
33243324
expect(called).toBe(true);
33253325
}));
33263326

3327+
it('should always be invoked if flagged as $stateful when wrapping one-time',
3328+
inject(function($parse) {
3329+
3330+
var interceptorCalls = 0;
3331+
function interceptor() {
3332+
interceptorCalls++;
3333+
return 123;
3334+
}
3335+
interceptor.$stateful = true;
3336+
3337+
scope.$watch($parse('::a', interceptor));
3338+
3339+
interceptorCalls = 0;
3340+
scope.$digest();
3341+
expect(interceptorCalls).not.toBe(0);
3342+
3343+
interceptorCalls = 0;
3344+
scope.$digest();
3345+
expect(interceptorCalls).not.toBe(0);
3346+
}));
3347+
3348+
it('should always be invoked if flagged as $stateful when wrapping one-time with inputs',
3349+
inject(function($parse) {
3350+
3351+
$filterProvider.register('identity', valueFn(identity));
3352+
3353+
var interceptorCalls = 0;
3354+
function interceptor() {
3355+
interceptorCalls++;
3356+
return 123;
3357+
}
3358+
interceptor.$stateful = true;
3359+
3360+
scope.$watch($parse('::a | identity', interceptor));
3361+
3362+
interceptorCalls = 0;
3363+
scope.$digest();
3364+
expect(interceptorCalls).not.toBe(0);
3365+
3366+
interceptorCalls = 0;
3367+
scope.$digest();
3368+
expect(interceptorCalls).not.toBe(0);
3369+
}));
3370+
3371+
it('should always be invoked if flagged as $stateful when wrapping one-time literal',
3372+
inject(function($parse) {
3373+
3374+
var interceptorCalls = 0;
3375+
function interceptor() {
3376+
interceptorCalls++;
3377+
return 123;
3378+
}
3379+
interceptor.$stateful = true;
3380+
3381+
scope.$watch($parse('::[a]', interceptor));
3382+
3383+
// Would be great if interceptor-output was checked for changes and this didn't throw...
3384+
interceptorCalls = 0;
3385+
expect(function() { scope.$digest(); }).toThrowMinErr('$rootScope', 'infdig');
3386+
expect(interceptorCalls).not.toBe(0);
3387+
3388+
interceptorCalls = 0;
3389+
expect(function() { scope.$digest(); }).toThrowMinErr('$rootScope', 'infdig');
3390+
expect(interceptorCalls).not.toBe(0);
3391+
}));
3392+
33273393
it('should not be invoked unless the input changes', inject(function($parse) {
33283394
var called = false;
33293395
function interceptor(v) {
@@ -3434,6 +3500,29 @@ describe('parser', function() {
34343500
scope.$digest();
34353501
expect(scope.$$watchersCount).toBe(0);
34363502
}));
3503+
3504+
it('should not propogate $$watchDelegate to the interceptor wrapped expression', inject(function($parse) {
3505+
function getter(s) {
3506+
return s.x;
3507+
}
3508+
getter.$$watchDelegate = getter;
3509+
3510+
function doubler(v) {
3511+
return 2 * v;
3512+
}
3513+
3514+
var lastValue;
3515+
function watcher(val) {
3516+
lastValue = val;
3517+
}
3518+
scope.$watch($parse(getter, doubler), watcher);
3519+
3520+
scope.$apply('x = 1');
3521+
expect(lastValue).toBe(2 * 1);
3522+
3523+
scope.$apply('x = 123');
3524+
expect(lastValue).toBe(2 * 123);
3525+
}));
34373526
});
34383527

34393528
describe('literals', function() {

0 commit comments

Comments
 (0)