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

Commit c7813e9

Browse files
committed
fix(ngAnimate): run structural animations with cancelled out class changes
When multiple animations on the same element are queued before a $digest passes, the animator tries to create as few actual animations as possible by joining / canceling redundant animations. Class-based animations for example are cancelled when the classes that are added and removed are the same, and the result is no class-change. This however must only happen if there's no structural animation currently queued. Fixes #14249
1 parent ef91b04 commit c7813e9

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
lines changed

src/ngAnimate/animateQueue.js

+5
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
8282
});
8383

8484
rules.cancel.push(function(element, newAnimation, currentAnimation) {
85+
// cancel the animation if classes added / removed in both animation cancel each other out,
86+
// but only if the current animation isn't structural
87+
88+
if (currentAnimation.structural) return false;
89+
8590
var nA = newAnimation.addClass;
8691
var nR = newAnimation.removeClass;
8792
var cA = currentAnimation.addClass;

test/ngAnimate/animateSpec.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,8 @@ describe("animations", function() {
11041104
$animate.removeClass(element, 'active-class');
11051105
$rootScope.$digest();
11061106

1107-
expect(doneHandler).toHaveBeenCalled();
1107+
// true = rejected
1108+
expect(doneHandler).toHaveBeenCalledWith(true);
11081109
}));
11091110

11101111
it('should cancel the previously running removeClass animation if a follow-up addClass animation is using the same class value',
@@ -1123,7 +1124,8 @@ describe("animations", function() {
11231124
$animate.addClass(element, 'active-class');
11241125
$rootScope.$digest();
11251126

1126-
expect(doneHandler).toHaveBeenCalled();
1127+
// true = rejected
1128+
expect(doneHandler).toHaveBeenCalledWith(true);
11271129
}));
11281130

11291131
it('should merge a follow-up animation that does not add classes into the previous animation (pre-digest)',
@@ -1198,6 +1200,29 @@ describe("animations", function() {
11981200

11991201
expect(capturedAnimation[2].addClass).toBe('blue');
12001202
}));
1203+
1204+
it('should NOT cancel a previously joined addClass+structural animation if a follow-up ' +
1205+
'removeClass animation is using the same class value (pre-digest)',
1206+
inject(function($animate, $rootScope) {
1207+
1208+
var runner = $animate.enter(element, parent);
1209+
$animate.addClass(element, 'active-class');
1210+
1211+
var doneHandler = jasmine.createSpy('enter done');
1212+
runner.done(doneHandler);
1213+
1214+
expect(doneHandler).not.toHaveBeenCalled();
1215+
1216+
$animate.removeClass(element, 'active-class');
1217+
$rootScope.$digest();
1218+
1219+
expect(capturedAnimation[1]).toBe('enter');
1220+
expect(capturedAnimation[2].addClass).toBe(null);
1221+
expect(capturedAnimation[2].removeClass).toBe(null);
1222+
1223+
expect(doneHandler).not.toHaveBeenCalled();
1224+
}));
1225+
12011226
});
12021227

12031228
describe('should merge', function() {

test/ngAnimate/integrationSpec.js

+38
Original file line numberDiff line numberDiff line change
@@ -756,5 +756,43 @@ describe('ngAnimate integration tests', function() {
756756
expect(child.attr('style')).toContain('50px');
757757
});
758758
});
759+
760+
761+
it('should execute the enter animation on a <form> with ngIf that has an ' +
762+
'<input type="email" required>', function() {
763+
764+
var animationSpy = jasmine.createSpy();
765+
766+
module(function($animateProvider) {
767+
$animateProvider.register('.animate-me', function() {
768+
return {
769+
enter: function(element, done) {
770+
animationSpy();
771+
done();
772+
}
773+
};
774+
});
775+
});
776+
777+
inject(function($animate, $rootScope, $compile) {
778+
779+
element = jqLite(
780+
'<div>' +
781+
'<form class="animate-me" ng-if="show">' +
782+
'<input ng-model="myModel" type="email" required />' +
783+
'</form>' +
784+
'</div>');
785+
786+
html(element);
787+
788+
$compile(element)($rootScope);
789+
790+
$rootScope.show = true;
791+
$rootScope.$digest();
792+
793+
$animate.flush();
794+
expect(animationSpy).toHaveBeenCalled();
795+
});
796+
});
759797
});
760798
});

0 commit comments

Comments
 (0)