Skip to content

Commit fa136cc

Browse files
committed
fix(expansion-panel): picking up lazy content from child component
We use a `ContentChild` to determine what lazy content to render inside an expansion panel. When the lazy content is nested further down inside another expansion panel, all ancestor panels up the tree will pick up the lowest lazy content from the lowest level. These changes add a check to ensure that the lazy content is rendered out by the closest panel. Fixes #14365.
1 parent ac97157 commit fa136cc

File tree

4 files changed

+74
-3
lines changed

4 files changed

+74
-3
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {InjectionToken} from '@angular/core';
10+
import {CdkAccordionItem} from '@angular/cdk/accordion';
11+
12+
/**
13+
* Base interface for a `MatExpansionPanel`.
14+
* @docs-private
15+
*/
16+
export interface MatExpansionPanelBase extends CdkAccordionItem {
17+
/** Whether the toggle indicator should be hidden. */
18+
hideToggle: boolean;
19+
}
20+
21+
/**
22+
* Token used to provide a `MatExpansionPanel` to `MatExpansionPanelContent`.
23+
* Used to avoid circular imports between `MatExpansionPanel` and `MatExpansionPanelContent`.
24+
*/
25+
export const MAT_EXPANSION_PANEL = new InjectionToken<MatExpansionPanelBase>('MAT_EXPANSION_PANEL');

src/lib/expansion/expansion-panel-content.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Directive, TemplateRef} from '@angular/core';
9+
import {Directive, TemplateRef, Inject, Optional} from '@angular/core';
10+
import {MAT_EXPANSION_PANEL, MatExpansionPanelBase} from './expansion-panel-base';
1011

1112
/**
1213
* Expansion panel content that will be rendered lazily
@@ -16,5 +17,7 @@ import {Directive, TemplateRef} from '@angular/core';
1617
selector: 'ng-template[matExpansionPanelContent]'
1718
})
1819
export class MatExpansionPanelContent {
19-
constructor(public _template: TemplateRef<any>) {}
20+
constructor(
21+
public _template: TemplateRef<any>,
22+
@Inject(MAT_EXPANSION_PANEL) @Optional() public _expansionPanel?: MatExpansionPanelBase) {}
2023
}

src/lib/expansion/expansion-panel.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {filter, startWith, take, distinctUntilChanged} from 'rxjs/operators';
3939
import {matExpansionAnimations} from './expansion-animations';
4040
import {MatExpansionPanelContent} from './expansion-panel-content';
4141
import {MAT_ACCORDION, MatAccordionBase} from './accordion-base';
42+
import {MAT_EXPANSION_PANEL} from './expansion-panel-base';
4243

4344
/** MatExpansionPanel's states. */
4445
export type MatExpansionPanelState = 'expanded' | 'collapsed';
@@ -67,6 +68,7 @@ let uniqueId = 0;
6768
// Provide MatAccordion as undefined to prevent nested expansion panels from registering
6869
// to the same accordion.
6970
{provide: MAT_ACCORDION, useValue: undefined},
71+
{provide: MAT_EXPANSION_PANEL, useExisting: MatExpansionPanel}
7072
],
7173
host: {
7274
'class': 'mat-expansion-panel',
@@ -162,7 +164,7 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI
162164
}
163165

164166
ngAfterContentInit() {
165-
if (this._lazyContent) {
167+
if (this._lazyContent && this._lazyContent._expansionPanel === this) {
166168
// Render the content as soon as the panel becomes open.
167169
this.opened.pipe(
168170
startWith<void>(null!),

src/lib/expansion/expansion.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('MatExpansionPanel', () => {
2020
PanelWithCustomMargin,
2121
LazyPanelWithContent,
2222
LazyPanelOpenOnLoad,
23+
NestedLazyPanelWithContent,
2324
PanelWithTwoWayBinding,
2425
],
2526
});
@@ -65,6 +66,28 @@ describe('MatExpansionPanel', () => {
6566
.toContain('Some content', 'Expected content to be rendered.');
6667
}));
6768

69+
it('should not render lazy content from a child panel inside the parent', fakeAsync(() => {
70+
const fixture = TestBed.createComponent(NestedLazyPanelWithContent);
71+
fixture.componentInstance.parentExpanded = true;
72+
fixture.detectChanges();
73+
74+
const parentContent: HTMLElement =
75+
fixture.nativeElement.querySelector('.parent-panel .mat-expansion-panel-content');
76+
const childContent: HTMLElement =
77+
fixture.nativeElement.querySelector('.child-panel .mat-expansion-panel-content');
78+
79+
expect(parentContent.textContent!.trim())
80+
.toBe('Parent content', 'Expected only parent content to be rendered.');
81+
expect(childContent.textContent!.trim())
82+
.toBe('', 'Expected child content element to be empty.');
83+
84+
fixture.componentInstance.childExpanded = true;
85+
fixture.detectChanges();
86+
87+
expect(childContent.textContent!.trim())
88+
.toBe('Child content', 'Expected child content element to be rendered.');
89+
}));
90+
6891
it('emit correct events for change in panel expanded state', () => {
6992
const fixture = TestBed.createComponent(PanelWithContent);
7093
fixture.componentInstance.expanded = true;
@@ -454,3 +477,21 @@ class LazyPanelOpenOnLoad {}
454477
class PanelWithTwoWayBinding {
455478
expanded = false;
456479
}
480+
481+
482+
@Component({
483+
template: `
484+
<mat-expansion-panel class="parent-panel" [expanded]="parentExpanded">
485+
Parent content
486+
487+
<mat-expansion-panel class="child-panel" [expanded]="childExpanded">
488+
<ng-template matExpansionPanelContent>Child content</ng-template>
489+
</mat-expansion-panel>
490+
</mat-expansion-panel>
491+
`
492+
})
493+
class NestedLazyPanelWithContent {
494+
parentExpanded = false;
495+
childExpanded = false;
496+
}
497+

0 commit comments

Comments
 (0)