Navigation Menu

Skip to content

Commit

Permalink
fix(animations): enable shadowElements to leave when their parent does (
Browse files Browse the repository at this point in the history
#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
  • Loading branch information
dario-piotrowicz authored and dylhunn committed Jun 23, 2022
1 parent ca6019e commit 999aca8
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 5 deletions.
Expand Up @@ -314,11 +314,13 @@ export class AnimationTransitionNamespace {

private _signalRemovalForInnerTriggers(rootElement: any, context: any) {
const elements = this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true);

const shadowElements = rootElement.shadowRoot ?
this._engine.driver.query(rootElement.shadowRoot, NG_TRIGGER_SELECTOR, true) :
[];
// emulate a leave animation for all inner nodes within this node.
// If there are no animations found for any of the nodes then clear the cache
// for the element.
elements.forEach(elm => {
[...elements, ...shadowElements].forEach(elm => {
// this means that an inner remove() operation has already kicked off
// the animation on this element...
if (elm[REMOVAL_FLAG]) return;
Expand Down Expand Up @@ -402,7 +404,9 @@ export class AnimationTransitionNamespace {

removeNode(element: any, context: any): void {
const engine = this._engine;
if (element.childElementCount) {
const elementHasChildren = !!element.childElementCount;
const elementHasShadowChildren = !!(element.shadowRoot && element.shadowRoot.childElementCount);
if (elementHasChildren || elementHasShadowChildren) {
this._signalRemovalForInnerTriggers(element, context);
}

Expand Down
62 changes: 60 additions & 2 deletions packages/core/test/animation/animation_query_integration_spec.ts
Expand Up @@ -5,13 +5,13 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {animate, animateChild, AnimationPlayer, AUTO_STYLE, group, query, sequence, stagger, state, style, transition, trigger, ɵAnimationGroupPlayer as AnimationGroupPlayer} from '@angular/animations';
import {animate, animateChild, AnimationEvent, AnimationPlayer, AUTO_STYLE, group, query, sequence, stagger, state, style, transition, trigger, ɵAnimationGroupPlayer as AnimationGroupPlayer} from '@angular/animations';
import {AnimationDriver, ɵAnimationEngine, ɵnormalizeKeyframes as normalizeKeyframes} from '@angular/animations/browser';
import {TransitionAnimationPlayer} from '@angular/animations/browser/src/render/transition_animation_engine';
import {ENTER_CLASSNAME, LEAVE_CLASSNAME} from '@angular/animations/browser/src/util';
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';
import {CommonModule} from '@angular/common';
import {Component, HostBinding, ViewChild} from '@angular/core';
import {Component, HostBinding, ViewChild, ViewEncapsulation} from '@angular/core';
import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

Expand Down Expand Up @@ -2832,6 +2832,64 @@ describe('animation query tests', function() {
]);
}));

it(`should emulate a leave animation on a child elements when a parent component using shadowDom leaves the DOM`,
fakeAsync(() => {
let childLeaveLog = 0;

@Component({
selector: 'ani-host',
template: `<ani-parent *ngIf="exp"></ani-parent>`,
})
class HostCmp {
public exp: boolean = false;
}

@Component({
selector: 'ani-parent',
encapsulation: ViewEncapsulation.ShadowDom,
template: `
<div @childAnimation (@childAnimation.start)="logChildLeave($event)"></div>
`,
animations: [
trigger(
'childAnimation',
[
transition(':leave', []),
]),
]
})
class ParentCmp {
logChildLeave(event: AnimationEvent) {
if (event.toState === 'void') {
childLeaveLog++;
}
}
}

TestBed.configureTestingModule({declarations: [ParentCmp, HostCmp]});

const fixture = TestBed.createComponent(HostCmp);
const cmp = fixture.componentInstance;

const updateExpAndFlush = (value: boolean) => {
cmp.exp = value;
fixture.detectChanges();
flushMicrotasks();
};

updateExpAndFlush(true);
expect(childLeaveLog).toEqual(0);

updateExpAndFlush(false);
expect(childLeaveLog).toEqual(1);

updateExpAndFlush(true);
expect(childLeaveLog).toEqual(1);

updateExpAndFlush(false);
expect(childLeaveLog).toEqual(2);
}));

it('should build, but not run sub triggers when a parent animation is scheduled', () => {
@Component({
selector: 'parent-cmp',
Expand Down

0 comments on commit 999aca8

Please sign in to comment.