Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 613d0a3

Browse files
committed
fix($animate): abort class-based animations if the element is removed during digest
Prior to this fix, if the element is removed before the digest kicks off then it leads to an error when a class based animation is run. This fix ensures that the animation will not run at all if the element does not have a parent element. Closes #8796
1 parent cb85cbc commit 613d0a3

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed

src/ngAnimate/animate.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -1015,14 +1015,23 @@ angular.module('ngAnimate', ['ng'])
10151015
}
10161016

10171017
return cache.promise = runAnimationPostDigest(function(done) {
1018+
var parentElement = element.parent();
1019+
var elementNode = extractElementNode(element);
1020+
var parentNode = elementNode.parentNode;
1021+
// TODO(matsko): move this code into the animationsDisabled() function once #8092 is fixed
1022+
if (!parentNode || parentNode['$$NG_REMOVED'] || elementNode['$$NG_REMOVED']) {
1023+
done();
1024+
return;
1025+
}
1026+
10181027
var cache = element.data(STORAGE_KEY);
10191028
element.removeData(STORAGE_KEY);
10201029

10211030
var state = element.data(NG_ANIMATE_STATE) || {};
10221031
var classes = resolveElementClasses(element, cache, state.active);
10231032
return !classes
10241033
? done()
1025-
: performAnimation('setClass', classes, element, null, null, function() {
1034+
: performAnimation('setClass', classes, element, parentElement, null, function() {
10261035
$delegate.setClass(element, classes[0], classes[1]);
10271036
}, done);
10281037
});

test/ngAnimate/animateSpec.js

+95
Original file line numberDiff line numberDiff line change
@@ -3413,6 +3413,101 @@ describe("ngAnimate", function() {
34133413
});
34143414
});
34153415

3416+
it('should skip class-based animations if the element is removed before the digest occurs', function() {
3417+
var spy = jasmine.createSpy();
3418+
module(function($animateProvider) {
3419+
$animateProvider.register('.animated', function() {
3420+
return {
3421+
beforeAddClass : spy,
3422+
beforeRemoveClass : spy,
3423+
beforeSetClass : spy
3424+
};
3425+
});
3426+
});
3427+
inject(function($rootScope, $animate, $compile, $rootElement, $document) {
3428+
$animate.enabled(true);
3429+
3430+
var one = $compile('<div class="animated"></div>')($rootScope);
3431+
var two = $compile('<div class="animated"></div>')($rootScope);
3432+
var three = $compile('<div class="animated three"></div>')($rootScope);
3433+
3434+
$rootElement.append(one);
3435+
$rootElement.append(two);
3436+
angular.element($document[0].body).append($rootElement);
3437+
3438+
$animate.addClass(one, 'active-class');
3439+
one.remove();
3440+
3441+
$rootScope.$digest();
3442+
expect(spy).not.toHaveBeenCalled();
3443+
3444+
$animate.addClass(two, 'active-class');
3445+
3446+
$rootScope.$digest();
3447+
expect(spy).toHaveBeenCalled();
3448+
3449+
spy.reset();
3450+
$animate.removeClass(two, 'active-class');
3451+
two.remove();
3452+
3453+
$rootScope.$digest();
3454+
expect(spy).not.toHaveBeenCalled();
3455+
3456+
$animate.setClass(three, 'active-class', 'three');
3457+
three.remove();
3458+
3459+
$rootScope.$digest();
3460+
expect(spy).not.toHaveBeenCalled();
3461+
});
3462+
});
3463+
3464+
it('should skip class-based animations if ngRepeat has marked the element or its parent for removal', function() {
3465+
var spy = jasmine.createSpy();
3466+
module(function($animateProvider) {
3467+
$animateProvider.register('.animated', function() {
3468+
return {
3469+
beforeAddClass : spy,
3470+
beforeRemoveClass : spy,
3471+
beforeSetClass : spy
3472+
};
3473+
});
3474+
});
3475+
inject(function($rootScope, $animate, $compile, $rootElement, $document) {
3476+
$animate.enabled(true);
3477+
3478+
var element = $compile(
3479+
'<div>' +
3480+
' <div ng-repeat="item in items" class="animated">' +
3481+
' <span>{{ $index }}</span>' +
3482+
' </div>' +
3483+
'</div>'
3484+
)($rootScope);
3485+
3486+
$rootElement.append(element);
3487+
angular.element($document[0].body).append($rootElement);
3488+
3489+
$rootScope.items = [1,2,3];
3490+
$rootScope.$digest();
3491+
3492+
var child = element.find('div');
3493+
3494+
$animate.addClass(child, 'start-animation');
3495+
$rootScope.items = [2,3];
3496+
$rootScope.$digest();
3497+
3498+
expect(spy).not.toHaveBeenCalled();
3499+
3500+
var innerChild = element.find('span');
3501+
3502+
$animate.addClass(innerChild, 'start-animation');
3503+
$rootScope.items = [3];
3504+
$rootScope.$digest();
3505+
3506+
expect(spy).not.toHaveBeenCalled();
3507+
dealoc(element);
3508+
});
3509+
});
3510+
34163511
it('should call class-based animation callbacks in the correct order when animations are skipped', function() {
34173512
var continueAnimation;
34183513
module(function($animateProvider) {

0 commit comments

Comments
 (0)