Skip to content

Commit 999aca8

Browse files
dario-piotrowiczdylhunn
authored andcommitted
fix(animations): enable shadowElements to leave when their parent does (#46459)
when a component uses the shadowDom view encapsulation its children are not rendered as normal HTML children of the element but they are insterted in the element's shadowRoot, this causes the leave of the element not to be normally propagated to the shadow child elements, fix such issue resolves #46450 PR Close #46459
1 parent ca6019e commit 999aca8

File tree

2 files changed

+67
-5
lines changed

2 files changed

+67
-5
lines changed

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,13 @@ export class AnimationTransitionNamespace {
314314

315315
private _signalRemovalForInnerTriggers(rootElement: any, context: any) {
316316
const elements = this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true);
317-
317+
const shadowElements = rootElement.shadowRoot ?
318+
this._engine.driver.query(rootElement.shadowRoot, NG_TRIGGER_SELECTOR, true) :
319+
[];
318320
// emulate a leave animation for all inner nodes within this node.
319321
// If there are no animations found for any of the nodes then clear the cache
320322
// for the element.
321-
elements.forEach(elm => {
323+
[...elements, ...shadowElements].forEach(elm => {
322324
// this means that an inner remove() operation has already kicked off
323325
// the animation on this element...
324326
if (elm[REMOVAL_FLAG]) return;
@@ -402,7 +404,9 @@ export class AnimationTransitionNamespace {
402404

403405
removeNode(element: any, context: any): void {
404406
const engine = this._engine;
405-
if (element.childElementCount) {
407+
const elementHasChildren = !!element.childElementCount;
408+
const elementHasShadowChildren = !!(element.shadowRoot && element.shadowRoot.childElementCount);
409+
if (elementHasChildren || elementHasShadowChildren) {
406410
this._signalRemovalForInnerTriggers(element, context);
407411
}
408412

packages/core/test/animation/animation_query_integration_spec.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {animate, animateChild, AnimationPlayer, AUTO_STYLE, group, query, sequence, stagger, state, style, transition, trigger, ɵAnimationGroupPlayer as AnimationGroupPlayer} from '@angular/animations';
8+
import {animate, animateChild, AnimationEvent, AnimationPlayer, AUTO_STYLE, group, query, sequence, stagger, state, style, transition, trigger, ɵAnimationGroupPlayer as AnimationGroupPlayer} from '@angular/animations';
99
import {AnimationDriver, ɵAnimationEngine, ɵnormalizeKeyframes as normalizeKeyframes} from '@angular/animations/browser';
1010
import {TransitionAnimationPlayer} from '@angular/animations/browser/src/render/transition_animation_engine';
1111
import {ENTER_CLASSNAME, LEAVE_CLASSNAME} from '@angular/animations/browser/src/util';
1212
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';
1313
import {CommonModule} from '@angular/common';
14-
import {Component, HostBinding, ViewChild} from '@angular/core';
14+
import {Component, HostBinding, ViewChild, ViewEncapsulation} from '@angular/core';
1515
import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
1616
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
1717

@@ -2832,6 +2832,64 @@ describe('animation query tests', function() {
28322832
]);
28332833
}));
28342834

2835+
it(`should emulate a leave animation on a child elements when a parent component using shadowDom leaves the DOM`,
2836+
fakeAsync(() => {
2837+
let childLeaveLog = 0;
2838+
2839+
@Component({
2840+
selector: 'ani-host',
2841+
template: `<ani-parent *ngIf="exp"></ani-parent>`,
2842+
})
2843+
class HostCmp {
2844+
public exp: boolean = false;
2845+
}
2846+
2847+
@Component({
2848+
selector: 'ani-parent',
2849+
encapsulation: ViewEncapsulation.ShadowDom,
2850+
template: `
2851+
<div @childAnimation (@childAnimation.start)="logChildLeave($event)"></div>
2852+
`,
2853+
animations: [
2854+
trigger(
2855+
'childAnimation',
2856+
[
2857+
transition(':leave', []),
2858+
]),
2859+
]
2860+
})
2861+
class ParentCmp {
2862+
logChildLeave(event: AnimationEvent) {
2863+
if (event.toState === 'void') {
2864+
childLeaveLog++;
2865+
}
2866+
}
2867+
}
2868+
2869+
TestBed.configureTestingModule({declarations: [ParentCmp, HostCmp]});
2870+
2871+
const fixture = TestBed.createComponent(HostCmp);
2872+
const cmp = fixture.componentInstance;
2873+
2874+
const updateExpAndFlush = (value: boolean) => {
2875+
cmp.exp = value;
2876+
fixture.detectChanges();
2877+
flushMicrotasks();
2878+
};
2879+
2880+
updateExpAndFlush(true);
2881+
expect(childLeaveLog).toEqual(0);
2882+
2883+
updateExpAndFlush(false);
2884+
expect(childLeaveLog).toEqual(1);
2885+
2886+
updateExpAndFlush(true);
2887+
expect(childLeaveLog).toEqual(1);
2888+
2889+
updateExpAndFlush(false);
2890+
expect(childLeaveLog).toEqual(2);
2891+
}));
2892+
28352893
it('should build, but not run sub triggers when a parent animation is scheduled', () => {
28362894
@Component({
28372895
selector: 'parent-cmp',

0 commit comments

Comments
 (0)