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
This commit is contained in:
Martin Staffa
2016-03-16 17:39:22 +01:00
parent cbd048d893
commit cc1de81f5e
3 changed files with 70 additions and 2 deletions
+5
View File
@@ -82,6 +82,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
// cancel the animation if classes added / removed in both animation cancel each other out,
// but only if the current animation isn't structural
if (currentAnimation.structural) return false;
var nA = newAnimation.addClass;
var nR = newAnimation.removeClass;
var cA = currentAnimation.addClass;
+27 -2
View File
@@ -1104,7 +1104,8 @@ describe("animations", function() {
$animate.removeClass(element, 'active-class');
$rootScope.$digest();
expect(doneHandler).toHaveBeenCalled();
// true = rejected
expect(doneHandler).toHaveBeenCalledWith(true);
}));
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() {
$animate.addClass(element, 'active-class');
$rootScope.$digest();
expect(doneHandler).toHaveBeenCalled();
// true = rejected
expect(doneHandler).toHaveBeenCalledWith(true);
}));
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() {
expect(capturedAnimation[2].addClass).toBe('blue');
}));
it('should NOT cancel a previously joined addClass+structural animation if a follow-up ' +
'removeClass animation is using the same class value (pre-digest)',
inject(function($animate, $rootScope) {
var runner = $animate.enter(element, parent);
$animate.addClass(element, 'active-class');
var doneHandler = jasmine.createSpy('enter done');
runner.done(doneHandler);
expect(doneHandler).not.toHaveBeenCalled();
$animate.removeClass(element, 'active-class');
$rootScope.$digest();
expect(capturedAnimation[1]).toBe('enter');
expect(capturedAnimation[2].addClass).toBe(null);
expect(capturedAnimation[2].removeClass).toBe(null);
expect(doneHandler).not.toHaveBeenCalled();
}));
});
describe('should merge', function() {
+38
View File
@@ -756,5 +756,43 @@ describe('ngAnimate integration tests', function() {
expect(child.attr('style')).toContain('50px');
});
});
it('should execute the enter animation on a <form> with ngIf that has an ' +
'<input type="email" required>', function() {
var animationSpy = jasmine.createSpy();
module(function($animateProvider) {
$animateProvider.register('.animate-me', function() {
return {
enter: function(element, done) {
animationSpy();
done();
}
};
});
});
inject(function($animate, $rootScope, $compile) {
element = jqLite(
'<div>' +
'<form class="animate-me" ng-if="show">' +
'<input ng-model="myModel" type="email" required />' +
'</form>' +
'</div>');
html(element);
$compile(element)($rootScope);
$rootScope.show = true;
$rootScope.$digest();
$animate.flush();
expect(animationSpy).toHaveBeenCalled();
});
});
});
});