Skip to content

Commit 7855d97

Browse files
committed
fix(material/expansion): Expansion panel content flickers during animation for lazy-loaded custom height nested expansion panels
This fix introduces workaround for a `void => collapsed` animation that is causing improper behaviour for lazy-loaded body. First, it disables first `void => collapsed` animation after init to disable recalculation which fixes changing heights of inner panels. Second, it sets `mat-expansion-panel-hidden` class on content init and removes it on creating lazy-loaded body to remove flicker of parent expansion panel Fixes #22715
1 parent ef0fa05 commit 7855d97

File tree

4 files changed

+55
-7
lines changed

4 files changed

+55
-7
lines changed

src/material/expansion/expansion-panel.html

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
<ng-content select="mat-expansion-panel-header"></ng-content>
2-
<div class="mat-expansion-panel-content"
3-
role="region"
4-
[@bodyExpansion]="_getExpandedState()"
5-
(@bodyExpansion.done)="_bodyAnimationDone.next($event)"
6-
[attr.aria-labelledby]="_headerId"
7-
[id]="id"
8-
#body>
2+
<div
3+
class="mat-expansion-panel-content"
4+
role="region"
5+
[class.mat-expansion-panel-hidden]="_panelHidden"
6+
[@bodyExpansion]="_getExpandedState()"
7+
(@bodyExpansion.start)="_animationStarted()"
8+
(@bodyExpansion.done)="_bodyAnimationDone.next($event)"
9+
[attr.aria-labelledby]="_headerId"
10+
[id]="id"
11+
#body
12+
>
913
<div class="mat-expansion-panel-body">
1014
<ng-content></ng-content>
1115
<ng-template [cdkPortalOutlet]="_portal"></ng-template>

src/material/expansion/expansion-panel.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262
}
6363
}
6464

65+
.mat-expansion-panel-hidden {
66+
height: 0;
67+
}
68+
6569
.mat-expansion-panel-body {
6670
padding: 0 24px 16px;
6771
}

src/material/expansion/expansion-panel.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS =
9595
'[class.mat-expanded]': 'expanded',
9696
'[class._mat-animation-noopable]': '_animationMode === "NoopAnimations"',
9797
'[class.mat-expansion-panel-spacing]': '_hasSpacing()',
98+
'[@.disabled]': '_animationsDisabled',
9899
},
99100
})
100101
export class MatExpansionPanel
@@ -150,6 +151,12 @@ export class MatExpansionPanel
150151
/** Stream of body animation done events. */
151152
readonly _bodyAnimationDone = new Subject<AnimationEvent>();
152153

154+
/** Whether Angular animations in the panel body should be disabled. */
155+
_animationsDisabled = false;
156+
157+
/** Whether panel body should be hidden. */
158+
_panelHidden = false;
159+
153160
constructor(
154161
@Optional() @SkipSelf() @Inject(MAT_ACCORDION) accordion: MatAccordionBase,
155162
_changeDetectorRef: ChangeDetectorRef,
@@ -218,6 +225,9 @@ export class MatExpansionPanel
218225

219226
ngAfterContentInit() {
220227
if (this._lazyContent && this._lazyContent._expansionPanel === this) {
228+
this._panelHidden = !this.expanded;
229+
this._animationsDisabled = !this.expanded;
230+
221231
// Render the content as soon as the panel becomes open.
222232
this.opened
223233
.pipe(
@@ -227,6 +237,7 @@ export class MatExpansionPanel
227237
)
228238
.subscribe(() => {
229239
this._portal = new TemplatePortal(this._lazyContent._template, this._viewContainerRef);
240+
this._hidePanel();
230241
});
231242
}
232243
}
@@ -251,6 +262,31 @@ export class MatExpansionPanel
251262

252263
return false;
253264
}
265+
266+
_animationStarted() {
267+
// Currently the `bodyExpansion` animation has a `void => collapsed` transition which is
268+
// there to work around a bug in Angular (see #13088), however this introduces a different
269+
// issue. The new transition will cause to flicker in certain situations (see #22715), if the
270+
// consumer has set a inner lazy-loaded expansion panel's header height that is different from the
271+
// default one.
272+
// Part of work around is to disable animations on the body and re-enabling them after the first animation has run.
273+
// Ideally this wouldn't be necessary if we remove the `void => collapsed` transition, but we have
274+
// to wait for https://github.com/angular/angular/issues/18847 to be resolved.
275+
this._animationsDisabled = false;
276+
}
277+
278+
private _hidePanel(): void {
279+
// Currently the `bodyExpansion` animation has a `void => collapsed` transition which is
280+
// there to work around a bug in Angular (see #13088), however this introduces a different
281+
// issue. The new transition will cause to flicker in certain situations (see #22715), if the
282+
// consumer has set a inner lazy-loaded expansion panel's header height that is different from the
283+
// default one.
284+
// Part of work around is to set non-expanded panel's height to 0px with class on init
285+
// so that outer expansion panel can calculate height of its body properly.
286+
// Ideally this wouldn't be necessary if we remove the `void => collapsed` transition, but we have
287+
// to wait for https://github.com/angular/angular/issues/18847 to be resolved.
288+
this._panelHidden = false;
289+
}
254290
}
255291

256292
/**

tools/public_api_guard/material/expansion.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI
107107
readonly afterExpand: EventEmitter<void>;
108108
// (undocumented)
109109
_animationMode: string;
110+
_animationsDisabled: boolean;
111+
// (undocumented)
112+
_animationStarted(): void;
110113
_body: ElementRef<HTMLElement>;
111114
readonly _bodyAnimationDone: Subject<AnimationEvent_2>;
112115
close(): void;
@@ -125,6 +128,7 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI
125128
// (undocumented)
126129
ngOnDestroy(): void;
127130
open(): void;
131+
_panelHidden: boolean;
128132
_portal: TemplatePortal;
129133
toggle(): void;
130134
get togglePosition(): MatAccordionTogglePosition;

0 commit comments

Comments
 (0)