Skip to content

Commit 21bf33e

Browse files
committed
fix(ngAnimate): ensure that orphaned elements do not throw errors when animated
This fix ensures that for both `$animateCss` and `$animate` capture the error when a class-based animation takes place where the element is removed from the parent element sometime before or during the preparation stages the animation. Closes angular#11975
1 parent 4cef752 commit 21bf33e

File tree

3 files changed

+72
-0
lines changed

3 files changed

+72
-0
lines changed

src/ngAnimate/animateCss.js

+8
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
497497

498498
function init(element, options) {
499499
var node = getDomNode(element);
500+
if (!node || !node.parentNode) {
501+
return closeAndReturnNoopAnimator();
502+
}
503+
500504
options = prepareAnimationOptions(options);
501505

502506
var temporaryStyles = [];
@@ -782,6 +786,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
782786

783787
function start() {
784788
if (animationClosed) return;
789+
if (!node.parentNode) {
790+
close();
791+
return;
792+
}
785793

786794
var startTime, events = [];
787795

test/ngAnimate/animateCssSpec.js

+37
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,43 @@ describe("ngAnimate $animateCss", function() {
5151
describe('when active', function() {
5252
if (!browserSupportsCssAnimations()) return;
5353

54+
it("should silently quit the animation and not throw when an element is not attached to a parent during preparation",
55+
inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {
56+
57+
var element = jqLite('<div></div>');
58+
expect(function() {
59+
$animateCss(element, {
60+
duration: 1000,
61+
event: 'fake',
62+
to: fakeStyle
63+
}).start();
64+
}).not.toThrow();
65+
66+
expect(element).not.toHaveClass('fake');
67+
triggerAnimationStartFrame();
68+
expect(element).not.toHaveClass('fake-active');
69+
}));
70+
71+
it("should silently quit the animation and not throw when an element is not attached to a parent before starting",
72+
inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {
73+
74+
var element = jqLite('<div></div>');
75+
jqLite($document[0].body).append($rootElement);
76+
$rootElement.append(element);
77+
78+
$animateCss(element, {
79+
duration: 1000,
80+
addClass: 'wait-for-it',
81+
to: fakeStyle
82+
}).start();
83+
84+
element.remove();
85+
86+
expect(function() {
87+
triggerAnimationStartFrame();
88+
}).not.toThrow();
89+
}));
90+
5491
describe("rAF usage", function() {
5592
it("should buffer all requests into a single requestAnimationFrame call",
5693
inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {

test/ngAnimate/integrationSpec.js

+27
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,33 @@ describe('ngAnimate integration tests', function() {
105105
expect(animationCompleted).toBe(true);
106106
});
107107
});
108+
109+
it('should not throw an error if the element is orphaned before the CSS animation starts',
110+
inject(function($rootScope, $rootElement, $animate, $$rAF) {
111+
112+
ss.addRule('.animate-me', 'transition:2s linear all;');
113+
114+
var parent = jqLite('<div></div>');
115+
html(parent);
116+
117+
var element = jqLite('<div class="animate-me">DOING</div>');
118+
parent.append(element);
119+
120+
$animate.addClass(parent, 'on');
121+
$animate.addClass(element, 'on');
122+
$rootScope.$digest();
123+
124+
// this will run the first class-based animation
125+
$$rAF.flush();
126+
127+
element.remove();
128+
129+
expect(function() {
130+
$$rAF.flush();
131+
}).not.toThrow();
132+
133+
dealoc(element);
134+
}));
108135
});
109136

110137
describe('JS animations', function() {

0 commit comments

Comments
 (0)