diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index d6f7547991803..807939a228d4a 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -571,7 +571,7 @@ export class TransitionAnimationEngine { createNamespace(namespaceId: string, hostElement: any) { const ns = new AnimationTransitionNamespace(namespaceId, hostElement, this); - if (hostElement.parentNode) { + if (this.bodyNode && this.driver.containsElement(this.bodyNode, hostElement)) { this._balanceNamespaceList(ns, hostElement); } else { // defer this later until flush during when the host element has @@ -580,7 +580,7 @@ export class TransitionAnimationEngine { this.newHostElements.set(hostElement, ns); // given that this host element is apart of the animation code, it - // may or may not be inserted by a parent node that is an of an + // may or may not be inserted by a parent node that is of an // animation renderer type. If this happens then we can still have // access to this item when we query for :enter nodes. If the parent // is a renderer then the set data-structure will normalize the entry diff --git a/packages/core/test/animation/animation_query_integration_spec.ts b/packages/core/test/animation/animation_query_integration_spec.ts index 527fb0f58fe3b..ee403ec1daa41 100644 --- a/packages/core/test/animation/animation_query_integration_spec.ts +++ b/packages/core/test/animation/animation_query_integration_spec.ts @@ -2374,6 +2374,91 @@ describe('animation query tests', function() { ]); }); + it(`should emulate a leave animation on a nested sub component's inner elements when a parent leave animation occurs with animateChild`, + () => { + @Component({ + selector: 'ani-cmp', + template: ` +
+ +
+ `, + animations: [ + trigger( + 'myAnimation', + [ + transition( + ':leave', + [ + query('@*', animateChild()), + ]), + ]), + ] + }) + class ParentCmp { + public exp: boolean = true; + } + + @Component({ + selector: 'child-cmp', + template: ` + + ` + }) + class ChildCmp { + } + + @Component({ + selector: 'nested-child-cmp', + template: ` +
+
+
+ `, + animations: [ + trigger( + 'myChildAnimation', + [ + transition( + ':leave', + [ + style({opacity: 0}), + animate('1s', style({opacity: 1})), + ]), + ]), + ] + }) + class NestedChildCmp { + } + + TestBed.configureTestingModule({declarations: [ParentCmp, ChildCmp, NestedChildCmp]}); + + const engine = TestBed.inject(ɵAnimationEngine); + const fixture = TestBed.createComponent(ParentCmp); + const cmp = fixture.componentInstance; + + cmp.exp = true; + fixture.detectChanges(); + + cmp.exp = false; + fixture.detectChanges(); + + // Inspect the players of the AnimationEngine and not those from getLog. The latter only + // returns the actual animation players, which the parent leave animation is not part + // of given that it does not have animation instructions of its own. + const players = engine.players; + expect(players.length).toEqual(1); + const player = players[0] as TransitionAnimationPlayer; + const realPlayer = player.getRealPlayer() as MockAnimationPlayer; + + expect(player.element.classList.contains('parent')).toBeTruthy(); + expect(realPlayer.element.classList.contains('inner-div')).toBeTruthy(); + expect(realPlayer.keyframes).toEqual([ + {opacity: '0', offset: 0}, + {opacity: '1', offset: 1}, + ]); + }); + it('should not cause a removal of inner @trigger DOM nodes when a parent animation occurs', fakeAsync(() => { @Component({