fix(animations): not waiting for child animations to finish when removing parent in Ivy (#34702)

In #28162 we introduced an extra `removeNode` call for host elements which can cause the parent element to be removed before all child animations have finished. The issue is only in Ivy, because that the only place where we pass in the `isHostElement` flag. These changes fix the issue by not re-triggering the removal logic if the element has in-progress animations.

Fixes #33597.

PR Close #34702
This commit is contained in:
crisbeto 2020-01-09 18:35:53 +01:00 committed by Matias Niemelä
parent 15ae924035
commit 697f6a55a5
2 changed files with 55 additions and 5 deletions

View File

@ -437,11 +437,14 @@ export class AnimationTransitionNamespace {
if (containsPotentialParentTransition) {
engine.markElementAsRemoved(this.id, element, false, context);
} else {
// we do this after the flush has occurred such
// that the callbacks can be fired
engine.afterFlush(() => this.clearElementCache(element));
engine.destroyInnerAnimations(element);
engine._onRemovalComplete(element, context);
const removalFlag = element[REMOVAL_FLAG];
if (!removalFlag || removalFlag === NULL_REMOVAL_STATE) {
// we do this after the flush has occurred such
// that the callbacks can be fired
engine.afterFlush(() => this.clearElementCache(element));
engine.destroyInnerAnimations(element);
engine._onRemovalComplete(element, context);
}
}
}

View File

@ -932,6 +932,53 @@ const DEFAULT_COMPONENT_ID = '1';
expect(fixture.debugElement.nativeElement.children.length).toBe(0);
}));
it('should wait for child animations before removing parent', fakeAsync(() => {
@Component({
template: '<child-cmp *ngIf="exp" @parentTrigger></child-cmp>',
animations: [trigger(
'parentTrigger', [transition(':leave', [group([query('@*', animateChild())])])])]
})
class ParentCmp {
exp = true;
}
@Component({
selector: 'child-cmp',
template: '<p @childTrigger>Hello there</p>',
animations: [trigger(
'childTrigger',
[transition(
':leave', [style({opacity: 1}), animate('200ms', style({opacity: 0}))])])]
})
class ChildCmp {
}
TestBed.configureTestingModule({declarations: [ParentCmp, ChildCmp]});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(ParentCmp);
fixture.detectChanges();
engine.flush();
expect(getLog().length).toBe(0);
fixture.componentInstance.exp = false;
fixture.detectChanges();
expect(fixture.nativeElement.children.length).toBe(1);
engine.flush();
expect(getLog().length).toBe(1);
const player = getLog()[0];
expect(player.keyframes).toEqual([
{opacity: '1', offset: 0},
{opacity: '0', offset: 1},
]);
player.finish();
flushMicrotasks();
expect(fixture.nativeElement.children.length).toBe(0);
}));
// animationRenderer => nonAnimationRenderer
it('should trigger a leave animation when the outer components element binding updates on the host component element',
fakeAsync(() => {