Skip to content

Commit 01cc995

Browse files
JoostKmhevery
authored andcommitted
fix(animations): ensure consistent transition namespace ordering (#19854)
When including a component in a template, the component's host element is immediately appended as child of the parent node upon creation. Hence, `hostElement.parentNode` will be a valid reference. However, if the parent component is being inserted as an embedded view—through `ngIf` for example—then the parent's node itself will not have been inserted yet. This means that we cannot properly determine the position of the transition namespace, as any `containsElement` check will return false given that the partial DOM tree has not been inserted yet, even though it will be contained within an existing transition namespace once the partial tree is attached. This commit fixes the issue by not just looking at the existence of a parent node, but rather a more extensive check using the driver's `containsElement` method. PR Close #19854
1 parent 75bb931 commit 01cc995

File tree

2 files changed

+87
-2
lines changed

2 files changed

+87
-2
lines changed

packages/animations/browser/src/render/transition_animation_engine.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ export class TransitionAnimationEngine {
571571

572572
createNamespace(namespaceId: string, hostElement: any) {
573573
const ns = new AnimationTransitionNamespace(namespaceId, hostElement, this);
574-
if (hostElement.parentNode) {
574+
if (this.bodyNode && this.driver.containsElement(this.bodyNode, hostElement)) {
575575
this._balanceNamespaceList(ns, hostElement);
576576
} else {
577577
// defer this later until flush during when the host element has
@@ -580,7 +580,7 @@ export class TransitionAnimationEngine {
580580
this.newHostElements.set(hostElement, ns);
581581

582582
// given that this host element is apart of the animation code, it
583-
// may or may not be inserted by a parent node that is an of an
583+
// may or may not be inserted by a parent node that is of an
584584
// animation renderer type. If this happens then we can still have
585585
// access to this item when we query for :enter nodes. If the parent
586586
// is a renderer then the set data-structure will normalize the entry

packages/core/test/animation/animation_query_integration_spec.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2374,6 +2374,91 @@ describe('animation query tests', function() {
23742374
]);
23752375
});
23762376

2377+
it(`should emulate a leave animation on a nested sub component's inner elements when a parent leave animation occurs with animateChild`,
2378+
() => {
2379+
@Component({
2380+
selector: 'ani-cmp',
2381+
template: `
2382+
<div @myAnimation *ngIf="exp" class="parent">
2383+
<child-cmp></child-cmp>
2384+
</div>
2385+
`,
2386+
animations: [
2387+
trigger(
2388+
'myAnimation',
2389+
[
2390+
transition(
2391+
':leave',
2392+
[
2393+
query('@*', animateChild()),
2394+
]),
2395+
]),
2396+
]
2397+
})
2398+
class ParentCmp {
2399+
public exp: boolean = true;
2400+
}
2401+
2402+
@Component({
2403+
selector: 'child-cmp',
2404+
template: `
2405+
<nested-child-cmp></nested-child-cmp>
2406+
`
2407+
})
2408+
class ChildCmp {
2409+
}
2410+
2411+
@Component({
2412+
selector: 'nested-child-cmp',
2413+
template: `
2414+
<section>
2415+
<div class="inner-div" @myChildAnimation></div>
2416+
</section>
2417+
`,
2418+
animations: [
2419+
trigger(
2420+
'myChildAnimation',
2421+
[
2422+
transition(
2423+
':leave',
2424+
[
2425+
style({opacity: 0}),
2426+
animate('1s', style({opacity: 1})),
2427+
]),
2428+
]),
2429+
]
2430+
})
2431+
class NestedChildCmp {
2432+
}
2433+
2434+
TestBed.configureTestingModule({declarations: [ParentCmp, ChildCmp, NestedChildCmp]});
2435+
2436+
const engine = TestBed.inject(ɵAnimationEngine);
2437+
const fixture = TestBed.createComponent(ParentCmp);
2438+
const cmp = fixture.componentInstance;
2439+
2440+
cmp.exp = true;
2441+
fixture.detectChanges();
2442+
2443+
cmp.exp = false;
2444+
fixture.detectChanges();
2445+
2446+
// Inspect the players of the AnimationEngine and not those from getLog. The latter only
2447+
// returns the actual animation players, which the parent leave animation is not part
2448+
// of given that it does not have animation instructions of its own.
2449+
const players = engine.players;
2450+
expect(players.length).toEqual(1);
2451+
const player = players[0] as TransitionAnimationPlayer;
2452+
const realPlayer = player.getRealPlayer() as MockAnimationPlayer;
2453+
2454+
expect(player.element.classList.contains('parent')).toBeTruthy();
2455+
expect(realPlayer.element.classList.contains('inner-div')).toBeTruthy();
2456+
expect(realPlayer.keyframes).toEqual([
2457+
{opacity: '0', offset: 0},
2458+
{opacity: '1', offset: 1},
2459+
]);
2460+
});
2461+
23772462
it('should not cause a removal of inner @trigger DOM nodes when a parent animation occurs',
23782463
fakeAsync(() => {
23792464
@Component({

0 commit comments

Comments
 (0)