Skip to content

Commit 7bf6422

Browse files
committed
feat($animate): allow directives to cancel animation events
Closes angular#7722
1 parent 2c7d085 commit 7bf6422

File tree

5 files changed

+149
-20
lines changed

5 files changed

+149
-20
lines changed

src/ng/animate.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ var $AnimateProvider = ['$provide', function($provide) {
127127
? after.after(element)
128128
: parent.prepend(element);
129129
async(done);
130+
return noop;
130131
},
131132

132133
/**
@@ -143,6 +144,7 @@ var $AnimateProvider = ['$provide', function($provide) {
143144
leave : function(element, done) {
144145
element.remove();
145146
async(done);
147+
return noop;
146148
},
147149

148150
/**
@@ -166,7 +168,7 @@ var $AnimateProvider = ['$provide', function($provide) {
166168
move : function(element, parent, after, done) {
167169
// Do not remove element before insert. Removing will cause data associated with the
168170
// element to be dropped. Insert will implicitly do the remove.
169-
this.enter(element, parent, after, done);
171+
return this.enter(element, parent, after, done);
170172
},
171173

172174
/**
@@ -183,13 +185,14 @@ var $AnimateProvider = ['$provide', function($provide) {
183185
* className value has been added to the element
184186
*/
185187
addClass : function(element, className, done) {
186-
className = isString(className) ?
187-
className :
188-
isArray(className) ? className.join(' ') : '';
188+
className = !isString(className)
189+
? (isArray(className) ? className.join(' ') : '')
190+
: className;
189191
forEach(element, function (element) {
190192
jqLiteAddClass(element, className);
191193
});
192194
async(done);
195+
return noop;
193196
},
194197

195198
/**
@@ -213,6 +216,7 @@ var $AnimateProvider = ['$provide', function($provide) {
213216
jqLiteRemoveClass(element, className);
214217
});
215218
async(done);
219+
return noop;
216220
},
217221

218222
/**
@@ -235,6 +239,7 @@ var $AnimateProvider = ['$provide', function($provide) {
235239
jqLiteRemoveClass(element, remove);
236240
});
237241
async(done);
242+
return noop;
238243
},
239244

240245
enabled : noop

src/ngAnimate/animate.js

+32-15
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,16 @@ angular.module('ngAnimate', ['ng'])
391391
return classNameFilter.test(className);
392392
};
393393

394+
function runAnimationPostDigest(fn) {
395+
var cancelFn;
396+
$rootScope.$$postDigest(function() {
397+
cancelFn = fn();
398+
});
399+
return function() {
400+
cancelFn && cancelFn();
401+
};
402+
}
403+
394404
function lookup(name) {
395405
if (name) {
396406
var matches = [],
@@ -614,6 +624,7 @@ angular.module('ngAnimate', ['ng'])
614624
* @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
615625
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
616626
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
627+
* @return {function} the animation cancellation function
617628
*/
618629
enter : function(element, parentElement, afterElement, doneCallback) {
619630
element = angular.element(element);
@@ -622,9 +633,8 @@ angular.module('ngAnimate', ['ng'])
622633

623634
this.enabled(false, element);
624635
$delegate.enter(element, parentElement, afterElement);
625-
$rootScope.$$postDigest(function() {
626-
element = stripCommentsFromElement(element);
627-
performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
636+
return runAnimationPostDigest(function() {
637+
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, doneCallback);
628638
});
629639
},
630640

@@ -657,13 +667,14 @@ angular.module('ngAnimate', ['ng'])
657667
*
658668
* @param {DOMElement} element the element that will be the focus of the leave animation
659669
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
670+
* @return {function} the animation cancellation function
660671
*/
661672
leave : function(element, doneCallback) {
662673
element = angular.element(element);
663674
cancelChildAnimations(element);
664675
this.enabled(false, element);
665-
$rootScope.$$postDigest(function() {
666-
performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
676+
return runAnimationPostDigest(function() {
677+
return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
667678
$delegate.leave(element);
668679
}, doneCallback);
669680
});
@@ -701,6 +712,7 @@ angular.module('ngAnimate', ['ng'])
701712
* @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
702713
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
703714
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
715+
* @return {function} the animation cancellation function
704716
*/
705717
move : function(element, parentElement, afterElement, doneCallback) {
706718
element = angular.element(element);
@@ -710,9 +722,8 @@ angular.module('ngAnimate', ['ng'])
710722
cancelChildAnimations(element);
711723
this.enabled(false, element);
712724
$delegate.move(element, parentElement, afterElement);
713-
$rootScope.$$postDigest(function() {
714-
element = stripCommentsFromElement(element);
715-
performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
725+
return runAnimationPostDigest(function() {
726+
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, doneCallback);
716727
});
717728
},
718729

@@ -744,11 +755,12 @@ angular.module('ngAnimate', ['ng'])
744755
* @param {DOMElement} element the element that will be animated
745756
* @param {string} className the CSS class that will be added to the element and then animated
746757
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
758+
* @return {function} the animation cancellation function
747759
*/
748760
addClass : function(element, className, doneCallback) {
749761
element = angular.element(element);
750762
element = stripCommentsFromElement(element);
751-
performAnimation('addClass', className, element, null, null, function() {
763+
return performAnimation('addClass', className, element, null, null, function() {
752764
$delegate.addClass(element, className);
753765
}, doneCallback);
754766
},
@@ -781,11 +793,12 @@ angular.module('ngAnimate', ['ng'])
781793
* @param {DOMElement} element the element that will be animated
782794
* @param {string} className the CSS class that will be animated and then removed from the element
783795
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
796+
* @return {function} the animation cancellation function
784797
*/
785798
removeClass : function(element, className, doneCallback) {
786799
element = angular.element(element);
787800
element = stripCommentsFromElement(element);
788-
performAnimation('removeClass', className, element, null, null, function() {
801+
return performAnimation('removeClass', className, element, null, null, function() {
789802
$delegate.removeClass(element, className);
790803
}, doneCallback);
791804
},
@@ -814,13 +827,14 @@ angular.module('ngAnimate', ['ng'])
814827
* removed from it
815828
* @param {string} add the CSS classes which will be added to the element
816829
* @param {string} remove the CSS class which will be removed from the element
817-
* @param {Function=} done the callback function (if provided) that will be fired after the
830+
* @param {function=} done the callback function (if provided) that will be fired after the
818831
* CSS classes have been set on the element
832+
* @return {function} the animation cancellation function
819833
*/
820834
setClass : function(element, add, remove, doneCallback) {
821835
element = angular.element(element);
822836
element = stripCommentsFromElement(element);
823-
performAnimation('setClass', [add, remove], element, null, null, function() {
837+
return performAnimation('setClass', [add, remove], element, null, null, function() {
824838
$delegate.setClass(element, add, remove);
825839
}, doneCallback);
826840
},
@@ -871,13 +885,14 @@ angular.module('ngAnimate', ['ng'])
871885
*/
872886
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
873887

888+
var noopCancel = noop;
874889
var runner = animationRunner(element, animationEvent, className);
875890
if(!runner) {
876891
fireDOMOperation();
877892
fireBeforeCallbackAsync();
878893
fireAfterCallbackAsync();
879894
closeAnimation();
880-
return;
895+
return noopCancel;
881896
}
882897

883898
className = runner.className;
@@ -908,7 +923,7 @@ angular.module('ngAnimate', ['ng'])
908923
fireBeforeCallbackAsync();
909924
fireAfterCallbackAsync();
910925
closeAnimation();
911-
return;
926+
return noopCancel;
912927
}
913928

914929
var skipAnimation = false;
@@ -956,7 +971,7 @@ angular.module('ngAnimate', ['ng'])
956971
fireBeforeCallbackAsync();
957972
fireAfterCallbackAsync();
958973
fireDoneCallbackAsync();
959-
return;
974+
return noopCancel;
960975
}
961976

962977
if(animationEvent == 'leave') {
@@ -1009,6 +1024,8 @@ angular.module('ngAnimate', ['ng'])
10091024
}
10101025
});
10111026

1027+
return runner.cancel;
1028+
10121029
function fireDOMCallback(animationPhase) {
10131030
var eventName = '$animate:' + animationPhase;
10141031
if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {

src/ngMock/angular-mocks.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
791791
element : arguments[0],
792792
args : arguments
793793
});
794-
$delegate[method].apply($delegate, arguments);
794+
return $delegate[method].apply($delegate, arguments);
795795
};
796796
});
797797

test/ng/animateSpec.js

+14
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ describe("$animate", function() {
5757
expect(element).toBeHidden();
5858
}));
5959

60+
it("should run each method and return a noop function", inject(function($animate, $document) {
61+
var element = jqLite('<div></div>');
62+
var move = jqLite('<div></div>');
63+
var parent = jqLite($document[0].body);
64+
parent.append(move);
65+
66+
expect($animate.enter(element, parent)).toBe(noop);
67+
expect($animate.move(element, move)).toBe(noop);
68+
expect($animate.addClass(element, 'on')).toBe(noop);
69+
expect($animate.addClass(element, 'off')).toBe(noop);
70+
expect($animate.setClass(element, 'on', 'off')).toBe(noop);
71+
expect($animate.leave(element)).toBe(noop);
72+
}));
73+
6074
it("should add and remove classes on SVG elements", inject(function($animate) {
6175
if (!window.SVGElement) return;
6276
var svg = jqLite('<svg><rect></rect></svg>');

test/ngAnimate/animateSpec.js

+93
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,99 @@ describe("ngAnimate", function() {
590590
}));
591591

592592

593+
it("should trigger a cancellation when the return function is called upon any animation", function() {
594+
var captures = {};
595+
596+
module(function($animateProvider) {
597+
$animateProvider.register('.track-me', function() {
598+
return {
599+
enter : track('enter'),
600+
leave : track('leave'),
601+
move : track('move'),
602+
addClass : track('addClass'),
603+
removeClass : track('removeClass'),
604+
setClass : track('setClass')
605+
};
606+
607+
function track(type) {
608+
return function(element, add, remove, done) {
609+
done = done || remove || add;
610+
return function(cancelled) {
611+
captures[type]=cancelled;
612+
};
613+
};
614+
}
615+
});
616+
});
617+
inject(function($animate, $sniffer, $rootScope, $timeout) {
618+
619+
var fn;
620+
$animate.enabled(true);
621+
$rootScope.$digest();
622+
element[0].removeChild(child[0]);
623+
child.addClass('track-me');
624+
625+
//enter
626+
fn = $animate.enter(child, element);
627+
$rootScope.$digest();
628+
$animate.triggerReflow();
629+
630+
expect(captures.enter).toBeUndefined();
631+
fn();
632+
expect(captures.enter).toBeTruthy();
633+
$animate.triggerCallbacks();
634+
635+
//move
636+
element.append(after);
637+
fn = $animate.move(child, element, after);
638+
$rootScope.$digest();
639+
$animate.triggerReflow();
640+
641+
expect(captures.move).toBeUndefined();
642+
fn();
643+
expect(captures.move).toBeTruthy();
644+
$animate.triggerCallbacks();
645+
646+
//addClass
647+
fn = $animate.addClass(child, 'ng-hide');
648+
$animate.triggerReflow();
649+
650+
expect(captures.addClass).toBeUndefined();
651+
fn();
652+
expect(captures.addClass).toBeTruthy();
653+
$animate.triggerCallbacks();
654+
655+
//removeClass
656+
fn = $animate.removeClass(child, 'ng-hide');
657+
$animate.triggerReflow();
658+
659+
expect(captures.removeClass).toBeUndefined();
660+
fn();
661+
expect(captures.removeClass).toBeTruthy();
662+
$animate.triggerCallbacks();
663+
664+
//setClass
665+
child.addClass('red');
666+
fn = $animate.setClass(child, 'blue', 'red');
667+
$animate.triggerReflow();
668+
669+
expect(captures.setClass).toBeUndefined();
670+
fn();
671+
expect(captures.setClass).toBeTruthy();
672+
$animate.triggerCallbacks();
673+
674+
//leave
675+
fn = $animate.leave(child);
676+
$rootScope.$digest();
677+
678+
expect(captures.leave).toBeUndefined();
679+
fn();
680+
expect(captures.leave).toBeTruthy();
681+
$animate.triggerCallbacks();
682+
});
683+
});
684+
685+
593686
it("should not run if animations are disabled",
594687
inject(function($animate, $rootScope, $timeout, $sniffer) {
595688

0 commit comments

Comments
 (0)