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

fix(ngAnimate): correctly animate transcluded clones with templateUrl #15514

Conversation

gkalpak
Copy link
Member

@gkalpak gkalpak commented Dec 16, 2016

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
Bug fix.

What is the current behavior? (You can also link to an open issue here)
$animate decides whether an animation should be cancelled based on some assumption that don't hold in specific cases (e.g. when animating transcluded clones with templateUrl directives on them for the first time). As a result, the entering elements will not be animated in such cases. This affects commonly used, structural built-in directives (ngIf, ngRepeat, ngSwitch etc).
See also #15510.

What is the new behavior (if this is a feature change)?
All entering animations work as expected (from the first time).

Does this PR introduce a breaking change?
No.

Please check if the PR fulfills these requirements

Other information:
BTW, I believe this is a side effect of lazily compiling transcluded content.

In addition to the last fix commit that fixes the bug, this PR also includes several refactor commits that simplify $animate internally, mainly by removing unnecessary wrapping/unwrapping of DOM nodes in jqLite/jQuery collections. This changes may also positively affect performance, especially in animation-heavy apps (although I haven't benchmarked it - yet it does indeed 😉).
(It is probably easier to review one commit at a time.)

Party addresses #14074 and #14124.

Fixes #15510.

@gkalpak
Copy link
Member Author

gkalpak commented Dec 17, 2016

I added one more commit with a (very basic) animation benchmark. It simply $animate.enters and immediatelly $animate.leaves 1000 elements - via ngRepeat. With the changes proposed in this commit, the benchmark shows a 5%-7% reduction in total execution time (which is not bad at all for a bunch of refatoring commits 😛 - I am tempted to change them to perf).

(Take the results with a grain of salt, as the benchmark scenario is a little artificial.)

var rootNode = getDomNode($rootElement);

var bodyNodeDetected = (node === bodyNode) || node.nodeName === 'HTML';
var rootNodeDetected = (node === rootNode);
Copy link
Member

Choose a reason for hiding this comment

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

Why the parens? (here & in other places)

Copy link
Member Author

Choose a reason for hiding this comment

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

A couple of reasons (at least):

  1. I find them more readable. I think it is a remnant from my early Java days (and this ancient document).
  2. It is also visually closer to isMatchingElement(...) 😛

(Hey, I changed the trailing dots, I am not removing the parens.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like we need an eslint rule for that :D But I prefer the parens too

Copy link
Member

Choose a reason for hiding this comment

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

I like not having parents here but as long as ESLint will shout at me if I forget them I'd be satisfied as well. ;)

$templateCache.put('foo.html', '<div>FOO</div>');
$compile(parent)($rootScope);

expect(capturedAnimation).toBe(null);
Copy link
Member

Choose a reason for hiding this comment

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

Everywhere in other places it's only checked if capturedAnimation is falsy. We should be consistent.

Copy link
Member Author

Choose a reason for hiding this comment

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

So, you want me to change all other places to toBe(null)? Fair enough...

Copy link
Member

Choose a reason for hiding this comment

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

I was thinking the other way round but maybe your idea has more sense. ;)

In some cases it's initially undefined, though, not null, this file is not very consistent in that regard. Perhaps that's material for a separate PR (or at least a commit) but I just want us to be consistent. :)

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

I (blindly) changed all occurrences of expect(capturedAnimation).toBeFalsy() to expect(capturedAnimation).toBe(null) and the tests were passing 😕

'use strict';

angular.
module('animationBenchmark', ['ngAnimate'], config).
Copy link
Member

Choose a reason for hiding this comment

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

I dislike this dot placement a lot. :P We use the leading dot in most places and I'd like to ESLint-enforce it one day...

Copy link
Member Author

Choose a reason for hiding this comment

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

Come on! I speeded up your animations 5%-7%. And all you care about is where I put a tiny little dot?

I'd like to ESLint-enforce it one day...

What a coincidence. I'd like to ESLint-enforce the trailing dot one day. So I am tryign to sneak in as many of them as possible, so when the day comes I can say: "Hey, look, there are more trailing dots than leading, let's stick with that 😛

I am afraid I am the only one around to favor the trailing dot, so I'll change it 😞

Copy link
Member

Choose a reason for hiding this comment

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

Come on! I speeded up your animations 5%-7%. And all you care about is where I put a tiny little dot?

No, I like this PR a lot! I'm just providing feedback for some small issues. :)

Good job overall, though, perhaps I should have spelled that out. 👏

Copy link
Member Author

Choose a reason for hiding this comment

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

Just in case it wasn't clear, I was just kidding (in an attempt to distract everyone from the fact that I was busted trying to sneak in those trailing dots 😛

[ng-cloak] { display: none !important; }
.animation-container .ng-enter,
.animation-container .ng-leave {
transition: all 0.1s
Copy link
Member

Choose a reason for hiding this comment

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

A missing semi-colon;

Copy link
Member Author

Choose a reason for hiding this comment

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

😱

@gkalpak gkalpak force-pushed the fix-ngAnimate-enter-transcluded-templateUrl branch from be4adcf to acd9e7b Compare December 21, 2016 12:14
@gkalpak
Copy link
Member Author

gkalpak commented Dec 21, 2016

Addressed (most of) @mgol's comments.

@@ -168,7 +168,7 @@ describe('animations', function() {
inject(function($animate, $rootScope) {
$animate.enter(element, parent);
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBe(null);
Copy link
Member

Choose a reason for hiding this comment

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

I'd put those changes in a separate commit as they don't really have a lot in common with the original one.

@gkalpak gkalpak force-pushed the fix-ngAnimate-enter-transcluded-templateUrl branch from acd9e7b to e1a8bd6 Compare December 31, 2016 10:58
@gkalpak gkalpak force-pushed the fix-ngAnimate-enter-transcluded-templateUrl branch from e1a8bd6 to c215896 Compare December 31, 2016 11:00
@gkalpak
Copy link
Member Author

gkalpak commented Dec 31, 2016

Rearranged the commits (and fixed a test failure).

// Note: We still need to use the old `node` for certain things, such as looking up in
// HashMaps where it was used as the key.

element = stripCommentsFromElement(originalElement);
Copy link
Contributor

Choose a reason for hiding this comment

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

I take it this has no performance implications? This now applies to all animations.

Copy link
Member Author

@gkalpak gkalpak Jan 4, 2017

Choose a reason for hiding this comment

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

I haven't benchmarked this change on its own. I assume it has an unnoticeable impact on performance (considering all the performance-heavy-ish stuff that is going on during animations).

But overall, this PR offers a non-negligible performance gain (see #15514 (comment)).

Previously, `$animate` would decide whether an animation should be cancelled
based on some assumption that didn't hold in specific cases (e.g. when animating
transcluded clones with `templateUrl` directives on them for the first time). As
a result, the entering elements would not be animated in such cases. This
affected commonly used, structural built-in directives (`ngIf`, `ngRepeat`,
`ngSwitch` etc).
This commit fixes it by avoiding invalid assumptions (i.e. by taking into
account the transformations that take place while compiling such elements).

Partly addresses angular#14074 and angular#14124.

Fixes angular#15510
@gkalpak gkalpak force-pushed the fix-ngAnimate-enter-transcluded-templateUrl branch from c215896 to 5341108 Compare January 9, 2017 10:13
@gkalpak
Copy link
Member Author

gkalpak commented Jan 9, 2017

@mgol, @Narretz: PTAL

@gkalpak gkalpak modified the milestones: 1.6.2, 1.5.11 Jan 11, 2017
@Narretz
Copy link
Contributor

Narretz commented Jan 20, 2017

LGTM
@mgol can you check this again?

@gkalpak gkalpak closed this in d24617b Jan 24, 2017
gkalpak added a commit that referenced this pull request Jan 24, 2017
Previously, `$animate` would decide whether an animation should be cancelled
based on some assumption that didn't hold in specific cases (e.g. when animating
transcluded clones with `templateUrl` directives on them for the first time). As
a result, the entering elements would not be animated in such cases. This
affected commonly used, structural built-in directives (`ngIf`, `ngRepeat`,
`ngSwitch` etc).
This commit fixes it by avoiding invalid assumptions (i.e. by taking into
account the transformations that take place while compiling such elements).

Partly addresses #14074 and #14124.

Fixes #15510

Closes #15514
@gkalpak gkalpak deleted the fix-ngAnimate-enter-transcluded-templateUrl branch January 24, 2017 22:11
ellimist pushed a commit to ellimist/angular.js that referenced this pull request Mar 15, 2017
Previously, `$animate` would decide whether an animation should be cancelled
based on some assumption that didn't hold in specific cases (e.g. when animating
transcluded clones with `templateUrl` directives on them for the first time). As
a result, the entering elements would not be animated in such cases. This
affected commonly used, structural built-in directives (`ngIf`, `ngRepeat`,
`ngSwitch` etc).
This commit fixes it by avoiding invalid assumptions (i.e. by taking into
account the transformations that take place while compiling such elements).

Partly addresses angular#14074 and angular#14124.

Fixes angular#15510

Closes angular#15514
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.

Enter/leave animation not working for for first item in ng-repeat
4 participants