diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index 02706c2b9f48..3b94a65101cf 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -564,7 +564,7 @@ angular.module('ngAnimate', ['ng']) //the animation if any matching animations are not found at all. //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found. if (animationsDisabled(element, parentElement) || matches.length === 0) { - domOperation(); + fireDOMOperation(); closeAnimation(); return; } @@ -597,7 +597,7 @@ angular.module('ngAnimate', ['ng']) //this would mean that an animation was not allowed so let the existing //animation do it's thing and close this one early if(animations.length === 0) { - domOperation(); + fireDOMOperation(); fireDoneCallbackAsync(); return; } @@ -617,7 +617,7 @@ angular.module('ngAnimate', ['ng']) //is so that the CSS classes present on the element can be properly examined. if((animationEvent == 'addClass' && element.hasClass(className)) || (animationEvent == 'removeClass' && !element.hasClass(className))) { - domOperation(); + fireDOMOperation(); fireDoneCallbackAsync(); return; } @@ -628,6 +628,7 @@ angular.module('ngAnimate', ['ng']) element.data(NG_ANIMATE_STATE, { running:true, + className:className, structural:!isClassBased, animations:animations, done:onBeforeAnimationsComplete @@ -638,7 +639,7 @@ angular.module('ngAnimate', ['ng']) invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete); function onBeforeAnimationsComplete(cancelled) { - domOperation(); + fireDOMOperation(); if(cancelled === true) { closeAnimation(); return; @@ -696,6 +697,15 @@ angular.module('ngAnimate', ['ng']) doneCallback && $timeout(doneCallback, 0, false); } + //it is less complicated to use a flag than managing and cancelling + //timeouts containing multiple callbacks. + function fireDOMOperation() { + if(!fireDOMOperation.hasBeenRun) { + fireDOMOperation.hasBeenRun = true; + domOperation(); + } + } + function closeAnimation() { if(!closeAnimation.hasBeenRun) { closeAnimation.hasBeenRun = true; diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index 8ad7ed796686..46bb9538a5df 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -2599,4 +2599,38 @@ describe("ngAnimate", function() { }); }); + it('should only perform the DOM operation once', + inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) { + + if (!$sniffer.transitions) return; + + ss.addRule('.base-class', '-webkit-transition:1s linear all;' + + 'transition:1s linear all;'); + + $animate.enabled(true); + + var element = $compile('
')($rootScope); + $rootElement.append(element); + jqLite($document[0].body).append($rootElement); + + $animate.removeClass(element, 'base-class one two'); + + //still true since we're before the reflow + expect(element.hasClass('base-class')).toBe(true); + + //this will cancel the remove animation + $animate.addClass(element, 'base-class one two'); + + //the cancellation was a success and the class was added right away + //since there was no successive animation for the after animation + expect(element.hasClass('base-class')).toBe(true); + + //the reflow... + $timeout.flush(); + + //the reflow DOM operation was commenced but it ran before so it + //shouldn't run agaun + expect(element.hasClass('base-class')).toBe(true); + })); + });