-
Notifications
You must be signed in to change notification settings - Fork 27.5k
perf(ngAnimate): avoid repeated calls to addClass/removeClass when an… #16613
Conversation
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
1 similar comment
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
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 won't pretend to understand everything that's going on, but looks reasonable (and there are tests that test stuff 😁)
this.$get = [function() { | ||
return { | ||
cacheKey: function(node, method, addClass, removeClass) { | ||
var parentNode = node.parentNode; |
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.
Isn't it possible for node.parentNode
to be undefined?
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.
Good question! You cannot animate elements that are not attached to the document / rootElement, and even an html element has a parentNode, so with this in mind, it's fine.
src/ngAnimate/shared.js
Outdated
@@ -314,7 +314,7 @@ function applyGeneratedPreparationClasses(element, event, options) { | |||
} | |||
if (classes.length) { | |||
options.preparationClasses = classes; | |||
element.addClass(classes); | |||
$$jqLite.addClass(element, classes); |
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.
Why this change?
return entry && entry.value; | ||
}, | ||
|
||
put: function(key, value, isValid) { |
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.
What happens when you can put
multiple times for the same key with different isValid
values?
Why isn't isValid
updated?
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 don't think a different isValid value is possible for the same cacheKey in one and the same animation run. The cacheKey is based on the classes on the element, and the classes define if there's an animation with duration on the element. Since a cacheKey is unique per class combination, it should not be possible to have different validity per cacheKey.
src/ngAnimate/animateCss.js
Outdated
@@ -377,20 +340,26 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro | |||
} | |||
} | |||
|
|||
// if a css animation has no duration we | |||
// should mark that so that repeated addClass/removeClass calls are skipped | |||
var hasNoDuration = allowNoDuration || (timings.transitionDuration > 0 || timings.animationDuration > 0); |
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.
Shouldn't this be named hasDuration
?
src/ngAnimate/animateCss.js
Outdated
@@ -903,10 +879,10 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro | |||
$$jqLite.addClass(element, activeClasses); | |||
|
|||
if (flags.recalculateTimingStyles) { | |||
fullClassName = node.getAttribute('class') + ' ' + preparationClasses; | |||
cacheKey = gcsHashFn(node, fullClassName); | |||
fullClassName = node.className + ' ' + preparationClasses; |
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.
className
can also be an instance ofSVGElement
if the element is anSVGAnimatedString
. It is better to get/set theclassName
of an element usingElement.getAttribute
andElement.setAttribute
if you are dealing with SVG elements.
src/ngAnimate/animation.js
Outdated
finalAnimations[i][j] = entry.fn; | ||
|
||
// the first row of elements shouldn't have a prepare-class added to them | ||
// since the elements are at the top of the animation hierarcy and they |
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.
hierarcy --> hierarchy_
if (removeClass) { | ||
parts.push(removeClass); | ||
} | ||
return parts.join(' '); |
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.
Is it a concerns that adding/removing the same class can result in the same key?
E.g. cacheKey(node, merhod, 'foo') === cacheKey(node, method, null, 'foo')
.
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 you try to remove a class, the cacheKey will contain the class to remove twice: once because the class is on the element, once for removeClass.
Since the animate functions bail out early when you try to remove a class that doesn't exist on the element, it's not possible that the key for addClass is the same as for removeClass.
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.
Thanks for the thorough review, @gkalpak I don't think the points are actionable, but it's possible that I missed something.
this.$get = [function() { | ||
return { | ||
cacheKey: function(node, method, addClass, removeClass) { | ||
var parentNode = node.parentNode; |
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.
Good question! You cannot animate elements that are not attached to the document / rootElement, and even an html element has a parentNode, so with this in mind, it's fine.
if (removeClass) { | ||
parts.push(removeClass); | ||
} | ||
return parts.join(' '); |
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 you try to remove a class, the cacheKey will contain the class to remove twice: once because the class is on the element, once for removeClass.
Since the animate functions bail out early when you try to remove a class that doesn't exist on the element, it's not possible that the key for addClass is the same as for removeClass.
return entry && entry.value; | ||
}, | ||
|
||
put: function(key, value, isValid) { |
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 don't think a different isValid value is possible for the same cacheKey in one and the same animation run. The cacheKey is based on the classes on the element, and the classes define if there's an animation with duration on the element. Since a cacheKey is unique per class combination, it should not be possible to have different validity per cacheKey.
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.
Other than the typos, LGTM 👍
test/ngAnimate/animateCacheSpec.js
Outdated
var validEntry = { someEssentialProperty: true }; | ||
var invalidEntry = { someEssentialProperty: false }; | ||
describe('containsCachedAnimationWithoutDuration', function() { | ||
it('should return false if the validity of an key is false', inject(function($$animateCache) { |
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.
an key --> a key
bc5c234
to
b13a6fa
Compare
…imation has no duration Background: ngAnimate writes helper classes to DOM elements to see if animations are defined on them. If many elements have the same definition, we can cache the definition and skip the application of the helper classes altogether. This helps particularly with large ngRepeat collections. Closes angular#14165 Closes angular#14166
b13a6fa
to
9463045
Compare
…imation has no duration Background: ngAnimate writes helper classes to DOM elements to see if animations are defined on them. If many elements have the same definition, and the same parent, we can cache the definition and skip the application of the helper classes altogether. This helps particularly with large ngRepeat collections. Closes #14165 Closes #14166 Closes #16613
…imation has no duration
Background:
ngAnimate writes helper classes to DOM elements to see if animations are defined on them. If many
elements have the same definition, we can cache the definition and skip the application of the
helper classes altogether. This helps particularly with large ngRepeat collections.
Closes #14165
Closes #14166
What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
What is the current behavior? (You can also link to an open issue here)
What is the new behavior (if this is a feature change)?
Does this PR introduce a breaking change?
Please check if the PR fulfills these requirements
Other information: