diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index 3c9ea9a971ad..838e210f5138 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -541,6 +541,16 @@ angular.module('ngAnimate', ['ng']) (ngAnimateState.done || noop)(); } + //There is no point in perform a class-based animation if the element already contains + //(on addClass) or doesn't contain (on removeClass) the className being animated. + //The reason why this is being called after the previous animations are cancelled + //is so that the CSS classes present on the element can be properly examined. + if((event == 'addClass' && element.hasClass(className)) || + (event == 'removeClass' && !element.hasClass(className))) { + onComplete && onComplete(); + return; + } + element.data(NG_ANIMATE_STATE, { running:true, structural:!isClassBased, diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index 3919dc532219..4d3fdd1ff3bb 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -345,6 +345,7 @@ describe("ngAnimate", function() { $animate.enabled(true); + element.addClass('ng-hide'); $animate.removeClass(element, 'ng-hide'); expect(element.text()).toBe('memento'); })); @@ -616,7 +617,7 @@ describe("ngAnimate", function() { ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); - element = $compile(html('
1
'))($rootScope); + element = $compile(html('
1
'))($rootScope); element.addClass('custom'); $animate.removeClass(element, 'ng-hide'); @@ -627,6 +628,7 @@ describe("ngAnimate", function() { expect(element.hasClass('ng-hide-remove-active')).toBe(true); } + element.removeClass('ng-hide'); $animate.addClass(element, 'ng-hide'); expect(element.hasClass('ng-hide-remove')).toBe(false); //added right away @@ -1052,14 +1054,17 @@ describe("ngAnimate", function() { }); describe("addClass / removeClass", function() { + var captured; beforeEach(function() { module(function($animateProvider, $provide) { $animateProvider.register('.klassy', function($timeout) { return { addClass : function(element, className, done) { + captured = 'addClass-' + className; $timeout(done, 500, false); }, removeClass : function(element, className, done) { + captured = 'removeClass-' + className; $timeout(done, 3000, false); } } @@ -1067,6 +1072,41 @@ describe("ngAnimate", function() { }); }); + it("should not perform an animation, and the followup DOM operation, if the class is " + + "already present during addClass or not present during removeClass on the element", + inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout) { + + var element = jqLite('
'); + $rootElement.append(element); + body.append($rootElement); + + //skipped animations + captured = 'none'; + $animate.removeClass(element, 'some-class'); + expect(element.hasClass('some-class')).toBe(false); + expect(captured).toBe('none'); + + element.addClass('some-class'); + + captured = 'nothing'; + $animate.addClass(element, 'some-class'); + expect(captured).toBe('nothing'); + expect(element.hasClass('some-class')).toBe(true); + + //actual animations + captured = 'none'; + $animate.removeClass(element, 'some-class'); + $timeout.flush(); + expect(element.hasClass('some-class')).toBe(false); + expect(captured).toBe('removeClass-some-class'); + + captured = 'nothing'; + $animate.addClass(element, 'some-class'); + $timeout.flush(); + expect(element.hasClass('some-class')).toBe(true); + expect(captured).toBe('addClass-some-class'); + })); + it("should add and remove CSS classes after an animation even if no animation is present", inject(function($animate, $rootScope, $sniffer, $rootElement) {