Skip to content

Commit 92dab0e

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 2adf629 commit 92dab0e

File tree

4 files changed

+74
-3
lines changed

4 files changed

+74
-3
lines changed
+25
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/material/expansion/expansion-panel-content.ts

+5-2
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/material/expansion/expansion-panel.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {filter, startWith, take, distinctUntilChanged} from 'rxjs/operators';
4040
import {matExpansionAnimations} from './expansion-animations';
4141
import {MatExpansionPanelContent} from './expansion-panel-content';
4242
import {MAT_ACCORDION, MatAccordionBase} from './accordion-base';
43+
import {MAT_EXPANSION_PANEL} from './expansion-panel-base';
4344

4445
/** MatExpansionPanel's states. */
4546
export type MatExpansionPanelState = 'expanded' | 'collapsed';
@@ -90,6 +91,7 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS =
9091
// Provide MatAccordion as undefined to prevent nested expansion panels from registering
9192
// to the same accordion.
9293
{provide: MAT_ACCORDION, useValue: undefined},
94+
{provide: MAT_EXPANSION_PANEL, useExisting: MatExpansionPanel}
9395
],
9496
host: {
9597
'class': 'mat-expansion-panel',
@@ -188,7 +190,7 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI
188190
}
189191

190192
ngAfterContentInit() {
191-
if (this._lazyContent) {
193+
if (this._lazyContent && this._lazyContent._expansionPanel === this) {
192194
// Render the content as soon as the panel becomes open.
193195
this.opened.pipe(
194196
startWith(null!),

src/material/expansion/expansion.spec.ts

+41
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe('MatExpansionPanel', () => {
2525
PanelWithCustomMargin,
2626
LazyPanelWithContent,
2727
LazyPanelOpenOnLoad,
28+
NestedLazyPanelWithContent,
2829
PanelWithTwoWayBinding,
2930
],
3031
});
@@ -70,6 +71,28 @@ describe('MatExpansionPanel', () => {
7071
.toContain('Some content', 'Expected content to be rendered.');
7172
}));
7273

74+
it('should not render lazy content from a child panel inside the parent', fakeAsync(() => {
75+
const fixture = TestBed.createComponent(NestedLazyPanelWithContent);
76+
fixture.componentInstance.parentExpanded = true;
77+
fixture.detectChanges();
78+
79+
const parentContent: HTMLElement =
80+
fixture.nativeElement.querySelector('.parent-panel .mat-expansion-panel-content');
81+
const childContent: HTMLElement =
82+
fixture.nativeElement.querySelector('.child-panel .mat-expansion-panel-content');
83+
84+
expect(parentContent.textContent!.trim())
85+
.toBe('Parent content', 'Expected only parent content to be rendered.');
86+
expect(childContent.textContent!.trim())
87+
.toBe('', 'Expected child content element to be empty.');
88+
89+
fixture.componentInstance.childExpanded = true;
90+
fixture.detectChanges();
91+
92+
expect(childContent.textContent!.trim())
93+
.toBe('Child content', 'Expected child content element to be rendered.');
94+
}));
95+
7396
it('emit correct events for change in panel expanded state', () => {
7497
const fixture = TestBed.createComponent(PanelWithContent);
7598
fixture.componentInstance.expanded = true;
@@ -490,3 +513,21 @@ class LazyPanelOpenOnLoad {}
490513
class PanelWithTwoWayBinding {
491514
expanded = false;
492515
}
516+
517+
518+
@Component({
519+
template: `
520+
<mat-expansion-panel class="parent-panel" [expanded]="parentExpanded">
521+
Parent content
522+
523+
<mat-expansion-panel class="child-panel" [expanded]="childExpanded">
524+
<ng-template matExpansionPanelContent>Child content</ng-template>
525+
</mat-expansion-panel>
526+
</mat-expansion-panel>
527+
`
528+
})
529+
class NestedLazyPanelWithContent {
530+
parentExpanded = false;
531+
childExpanded = false;
532+
}
533+

0 commit comments

Comments
 (0)