Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

perf(ngAnimate): avoid repeated calls to addClass/removeClass when an… #16613

Closed
wants to merge 1 commit into from

Conversation

Narretz
Copy link
Contributor

@Narretz Narretz commented Jun 25, 2018

…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

  • The commit message follows our guidelines
  • Fix/Feature: Docs have been added/updated
  • Fix/Feature: Tests have been added; existing tests pass

Other information:

@googlebot
Copy link

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 cla/google commit status will not change from this state. It's up to you to confirm consent of the commit author(s) and merge this pull request when appropriate.

1 similar comment
@googlebot
Copy link

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 cla/google commit status will not change from this state. It's up to you to confirm consent of the commit author(s) and merge this pull request when appropriate.

Copy link
Member

@gkalpak gkalpak left a 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;
Copy link
Member

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?

Copy link
Contributor Author

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.

@@ -314,7 +314,7 @@ function applyGeneratedPreparationClasses(element, event, options) {
}
if (classes.length) {
options.preparationClasses = classes;
element.addClass(classes);
$$jqLite.addClass(element, classes);
Copy link
Member

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) {
Copy link
Member

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?

Copy link
Contributor Author

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.

@@ -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);
Copy link
Member

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?

@@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to MDN:

className can also be an instance of SVGElement if the element is an SVGAnimatedString. It is better to get/set the className of an element using Element.getAttribute and Element.setAttribute if you are dealing with SVG elements.

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
Copy link
Member

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(' ');
Copy link
Member

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').

Copy link
Contributor Author

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.

Copy link
Contributor Author

@Narretz Narretz left a 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;
Copy link
Contributor Author

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(' ');
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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.

Copy link
Member

@gkalpak gkalpak left a 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 👍

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an key --> a key

@Narretz Narretz force-pushed the perf-animate-cache branch 2 times, most recently from bc5c234 to b13a6fa Compare July 3, 2018 19:37
…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
@Narretz Narretz closed this in 0e26197 Jul 5, 2018
Narretz pushed a commit that referenced this pull request Jul 5, 2018
…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
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ngAnimate doesn't cache failed animations to avoid addClass/removeClass
4 participants