-
Notifications
You must be signed in to change notification settings - Fork 27.4k
fix($parse): do not shallow-watch inputs when wrapped in an interceptor fn #16018
fix($parse): do not shallow-watch inputs when wrapped in an interceptor fn #16018
Conversation
@@ -622,6 +622,9 @@ function isStateless($filter, filterName) { | |||
return !fn.$stateful; | |||
} | |||
|
|||
var PURITY_ABSOLUTE = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better terminology for these...?
// Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a | ||
// potentially non-pure interceptor function. | ||
if (e.isPure === PURITY_RELATIVE) { | ||
return function depurifier(s) { return e(s); }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating these extra closures and executing them per-digest when watching things is a bit of a concern. But any alternative I've thought of would involve modifying https://github.com/jbedard/angular.js/blob/5c3f3ffb7478eadc72c4f90b6a1964557e355538/src/ng/parse.js#L1856 which would effect everything, not only interceptor inputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where is depurifier
defined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's just the name of this wrapper function (which removes the isPure
flag).
5c3f3ff
to
6699f60
Compare
test/ng/directive/ngClassSpec.js
Outdated
@@ -629,7 +645,7 @@ describe('ngClass', function() { | |||
})); | |||
|
|||
it('should not be copied when using one-time binding', inject(function($compile, $rootScope) { | |||
element = $compile('<div ng-class="::{foo: veryLargeObj, bar: bar}"></div>')($rootScope); | |||
element = $compile('<div ng-class="::{foo: !!veryLargeObj, bar: bar}"></div>')($rootScope); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the change I'd like to avoid in 1.6.x by reverting the ng-class half of 189461f in 1.6
6699f60
to
8edbf5c
Compare
@lgalfaso would be great to get a review from you if you have a chance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that this PR works as it solves the specific issue, but I think it does so at a very high cost. What is currently at $parse
are two things
- The lexer, tokenizer, expression generation and evaluation (this includes the two phase mechanism to only evaluate things once even when there is a change)
- One-time binding, unbinding on literals and all the other interceptor functionality, including watch delegates in its many forms
So far, the contract between these two parts was minimal, in theory it was possible to split these two into two different parts. This PR makes this theoretical separation hard. What would it take to get this separation back?
// Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a | ||
// potentially non-pure interceptor function. | ||
if (e.isPure === PURITY_RELATIVE) { | ||
return function depurifier(s) { return e(s); }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where is depurifier
defined?
I agree with keeping those two parts separate. In fact I've often been tempted to move part2 into rootScope.js because that stuff is really about how to watch expression, not about the expressions themselves. Basically parse.js would parse strings and output functions+metadata. That metadata would be today's constant, literal and inputs flags. Then rootScope may or may not use those properties to watch those functions. Then you could even declare your own functions and add these properties, this would replace things like the interpolate watch delegate with However I don't think this PR really makes it worse if you consider See #16015 which I think moves this direction a bit by separating |
fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; | ||
fn.inputs = (parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]).map(function(e) { | ||
// Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a | ||
// potentially non-pure interceptor function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand this comment and how this matches with the previous comment. Here it is stated that the interceptor can be non-pure, but before it is assumed that the interceptor is pure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the comment a few lines up is using the wrong terminology. I think it should just say "it is assumed to be non-$stateful". I'm not 100% sure if I'm using the "pure" terminology correct here, but I'll try to update the comments similar to #16015 which I think clears this up more. See: https://github.com/angular/angular.js/pull/16015/files#diff-780de070ede30180f6aedb6c5f7d57caR1964
To (try) summarizing:
- interceptors/filters can be
$stateful
in which case they must always be invoked to check for changes - inputs can be
pure
in which case they only need to be shallow watched - inputs passed into interceptors/filters are NOT pure because the interceptor/filter might inspect the content of them
- inputs that are pure in
$parse(exp)
might not be when doing$parse(exp, interceptor)
, but they share the sameinputs
array which is what this ugly.map
is for
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the comment above this to remove the (incorrect) use of "pure function" terminology.
With the clarifications and the changes in the comments, LGTM. |
a90bc21
to
6c8933d
Compare
6c8933d
to
869644e
Compare
Fixes #15905
This basically applies the same as b5118ac to interceptors.
For example normally when watching
[var]
thevar
only needs to be shallow-watched. But if that expression then gets wrapped in an interceptor such as$parse('[var]', interceptor)
we must assume theinterceptor
is non-pure and might read state from within thevar
(just like filters/functions...).This tries to preserve shallow watching of things behind operators such as
!
by distinguishing if an expression is pure due to an operator such as!
("absolute") or pure only if no parent operation wants to deep watch it ("relative"). This way when wrapped in an interceptor the "absolute" ones can still be shallow watched, while the "relative" ones that the interceptor has access to can no longer be shallow watched.This still doesn't fully restore the 1.6.3 ng-class functionality because it still doesn't do a deep-watch for one-time bindings like 1.6.3 did. The one (famous last words...) remaining regression from 1.6.3 is demonstrated by the modified ng-class spec (objects-in-literals-with-interceptors-in-one-time bindings). This is due to this annoying condition which I really wish I could find a better method of doing...