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

fix(ngAnimate): remove event prepare class when skipping animation #16677

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/ngAnimate/animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
var element = animationEntry.from ? animationEntry.from.element : animationEntry.element;
var extraClasses = options.addClass;
extraClasses = (extraClasses ? (extraClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
var cacheKey = $$animateCache.cacheKey(node, event, extraClasses, options.removeClass);
var cacheKey = $$animateCache.cacheKey(node, animationEntry.event, extraClasses, options.removeClass);
Copy link
Author

Choose a reason for hiding this comment

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

I'm still unable to isolate the steps which lead to the issue. While trying to find out I managed to get why it leaves on the containsCachedAnimationWithoutDuration() check. I can see that there is a leave and an enter animation getting triggered. Both are in the animation queue, but when code goes through the animations it creates the cache key based on the first event (leave in this case). When it later comes to the actual execution of the animation it first handles the leave, containsCachedAnimationWithoutDuration() returns false as this key is not in the cache yet and then adds it to cache later (with !valid as the transition-duration is null). Then later the same happens for the enter animation, but due to the same cache key as the leave animation containsCachedAnimationWithoutDuration() now finds the existing non valid entry and returns true and the enter is skipped.

Any idea how the duration can be null?

Copy link
Author

Choose a reason for hiding this comment

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

So I managed now to reproduce it

http://plnkr.co/edit/6CsoTul8LBIGBcHfioYJ
(hitting action, when text goes red it means we are into the situation).

So you need a directive and changing ng-switch and changing ng-class as far as I see. No idea how to put that into a test.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, I'll take a look. I take it the custom directive is necessary? replace: true always complicates things.

Copy link
Author

Choose a reason for hiding this comment

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

Any chance that one can trigger the animation events for an element in the tests? Might be simpler than rebuilding the complex structure.

Copy link
Contributor

Choose a reason for hiding this comment

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

There's animationSpec.js, which tests this behavior somewhat in isolation:

they('should remove the preparation class before the $prop-animation starts',
['enter', 'leave', 'move'], function(animationType) {
inject(function($$animation, $rootScope, $$rAF) {
var expectedClassName = 'ng-' + animationType + '-prepare';
var child = jqLite('<div></div>');
element.append(child);
$$animation(element, animationType);
$$animation(child, animationType);
$rootScope.$digest();
expect(element).not.toHaveClass(expectedClassName);
expect(child).toHaveClass(expectedClassName);
$$rAF.flush();
expect(element).not.toHaveClass(expectedClassName);
expect(child).not.toHaveClass(expectedClassName);
});
});

But since the cacheKey is created in animateCss, I'm not sure it's possible to recreate the case here.
Maybe we'll have to build a full integration test including the custom directive first.

Copy link
Contributor

Choose a reason for hiding this comment

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

I managed to trim down your plnkr to what I think is the minimum to reproduce the problem: http://plnkr.co/edit/m5CaegjmdtpR1pUX7zZ8?p=preview I'll try to put this into a test, then it should be easier to see what happens when. Interestingly, the ngClass directive is necessary for the bug to appear.

Any idea how the duration can be null?

ngAnimate is reading possible animation styles from the element during animation. So if you have an element / directive that could technically be animated, it will look on the classes to find animations. If none are found (basically the default), the animation duration is null. Or did you mean null as in null and not 0?

Copy link
Contributor

Choose a reason for hiding this comment

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

I've opened a PR #16681 that simplifies the fix and adds tests


toBeSortedAnimations.push({
element: element,
Expand All @@ -199,6 +199,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
// and it's in fact an invalid animation (something that has duration = 0)
// then we should skip all the heavy work from here on
if ($$animateCache.containsCachedAnimationWithoutDuration(cacheKey)) {
removePrepareClass();
closeFn();
return;
}
Expand Down Expand Up @@ -399,17 +400,21 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
}
}

function beforeStart() {
tempClasses = (tempClasses ? (tempClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
$$jqLite.addClass(element, tempClasses);

function removePrepareClass() {
var prepareClassName = element.data(PREPARE_CLASSES_KEY);
if (prepareClassName) {
$$jqLite.removeClass(element, prepareClassName);
prepareClassName = null;
}
}

function beforeStart() {
tempClasses = (tempClasses ? (tempClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
$$jqLite.addClass(element, tempClasses);

removePrepareClass();
}

function updateAnimationRunners(animation, newRunner) {
if (animation.from && animation.to) {
update(animation.from.element);
Expand Down