-
Notifications
You must be signed in to change notification settings - Fork 27.4k
perf(*): more performant interpolation and lazy one-time binding #7700
perf(*): more performant interpolation and lazy one-time binding #7700
Conversation
Thanks for the PR! Please check the items below to help us merge this faster. See the contributing docs for more information.
If you need to make changes to your pull request, you can update the commit with Thanks again for your help! |
This reverts commit cee429f. See angular#7700 for a more performant approach for bind-once.
I would like to know where is that we are spending this extra cycles in the current implementation (without thinking on one-time binding). Trying to tackle both things at the same time makes it a lot harder to understand the problem and what this patch is trying to solve |
cache[exp] = parsedExpression; | ||
} | ||
if (oneTime) parsedExpression.$$beWatched = oneTimeWatch; | ||
if (parsedExpression.constant) parsedExpression.$$beWatched = constantWatch; |
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.
if (parsedExpression.constant) parsedExpression.$$beWatched = constantWatch;
else if (oneTime) parsedExpression.$$beWatched = oneTimeWatch;
saves a comparison for the majority of cases
…pproach This change undoes the use of watchGroup by code that uses $interpolate, by moving the optimizations into the $interpolate itself. While this is not ideal, it means that we are making the existing api faster rather than require people to use $interpolate differently in order to benefit from the speed improvements.
This reverts commit cee429f. See angular#7700 for a more performant approach for bind-once.
#8029 is fixed by this (if the build weren't broken, I mean). Here's a bonus test case you can add to prevent that from being broken similarly in the future, it('should NOT insert null option when blurred without selection with bind-once options', function() {
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
scope.selected = scope.values[0];
createSelect({
'ng-model': 'selected',
'ng-options': 'value.name for value in ::values'
});
browserTrigger(element, 'blur');
expect(element.children('option').length).toBe(3);
}); I guess the real test is more like this: it('should use exact same values as values in scope with one-time bindings', function() {
scope.values = [{name: 'A'}, {name: 'B'}];
scope.selected = scope.values[0];
createSelect({
'ng-model': 'selected',
'ng-options': 'value.name for value in ::values'
});
browserTrigger(element.find('option').eq(1));
expect(scope.selected).toBe(scope.values[1]);
}); |
return (value || '').toString(); | ||
} | ||
var parsed = $parse(attr.ngBindHtml), | ||
changeDetector = $parse(attr.ngBindHtml, function getStringValue(value) { |
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.
just fyi that we prefer multiple var
s in new code as that makes refactoring easier.
|
||
forEach(watchExpressions, function (expr, i) { | ||
var exprFn = $parse(expr); | ||
deregisterFns.push(self.$watch(exprFn, function (value, oldValue) { | ||
var unwatch = self.$watch(expr, function wgExpression(value, oldValue) { |
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.
"wgExpression" -> "watchGroupSubAction" ?
This reverts commit cee429f. See angular#7700 for a more performant approach for bind-once.
Will this make it to the next release? If not, can we add the support for one-time binding of object literals (ng-class etc.) to the current implementation? I know it was in this PR, but I can't seem to find it atm. |
@Narretz It was in a commit on my fork (rodyhaddad@c9ee324), and it was for #7663 |
@rodyhaddad So once this is merged, the other change can be merged, too, right? |
This reverts commit cee429f. See angular#7700 for a more performant approach for bind-once.
This reverts commit cee429f. See angular#7700 for a more performant approach for bind-once.
BEAKING CHANGE: Lazy-binding now happens on the scope watcher level. What this means is that given `parseFn = $parse('::foo')`, bind-once will only kick in when `parseFn` is being watched by a scope (i.e. `scope.$watch(parseFn)`) Bind-once will have no effect when directily invoking `parseFn` (i.e. `parseFn()`)
This reverts commit cee429f. See angular#7700 for a more performant approach for bind-once.
This PR implements a different approach to one-time binding, which also gives us a performant improvement for $interpolate by about 33%.
Here's a benchmark app using the current snapshot: plunkr
Here's the same benchmark with this PR: plunkr
Look at the difference between the two
baseline interpolation
. It goes from 30-35ms to 20-25ms.This gets us closer the the
baseline binding
, which is about 15-20ms.The other 3 benchmarks in the benchmark app remain unchanged.
What's behind this change:
Expressions can say how they want to be watched
This allows the following:
interFn = $interpolate('{{a}}{{b}}')
, interFn can say that it wants to be watched by: watching each expression individually (i.e.scope.$watchGroup(['a', 'b']
)parseFn = $parse('1 + 1')
, parseFn can say that it wants to be watched by: getting evaluated on the next $digest, and then unregistering itself since its constant anywayparseFn = $parse('::foo')
, parseFn can say that it wants to be watched by: getting evaluated on the $digest as usual, and unregistering itself when its value is no longerundefined
(a.k.a. bind-once)InterceptionFn
There are cases where the last scenario doesn't work out as we'd like.
If we're watching a function that calls
parseFn
inside it, we lose the benefits mentioned above. That's becauseparseFn
isn't aware that it's being watched (indirectly). So I created an interceptorFn in$parse
that goes around this issue. Interceptors work as filters and you can easily see their application in ngBind.js or sce.jsI could go into more details, but you should check the code.
The first commit just reverts the current implementation of bind-once, so checking the second commit on it's own can make reviewing easier
Suggested order of review: