diff --git a/src/material/expansion/expansion-panel-base.ts b/src/material/expansion/expansion-panel-base.ts new file mode 100644 index 000000000000..2b64a08d0465 --- /dev/null +++ b/src/material/expansion/expansion-panel-base.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 {InjectionToken} from '@angular/core'; +import {CdkAccordionItem} from '@angular/cdk/accordion'; + +/** + * Base interface for a `MatExpansionPanel`. + * @docs-private + */ +export interface MatExpansionPanelBase extends CdkAccordionItem { + /** Whether the toggle indicator should be hidden. */ + hideToggle: boolean; +} + +/** + * Token used to provide a `MatExpansionPanel` to `MatExpansionPanelContent`. + * Used to avoid circular imports between `MatExpansionPanel` and `MatExpansionPanelContent`. + */ +export const MAT_EXPANSION_PANEL = new InjectionToken('MAT_EXPANSION_PANEL'); diff --git a/src/material/expansion/expansion-panel-content.ts b/src/material/expansion/expansion-panel-content.ts index b755fc8870f1..18e29a116bed 100644 --- a/src/material/expansion/expansion-panel-content.ts +++ b/src/material/expansion/expansion-panel-content.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {Directive, TemplateRef} from '@angular/core'; +import {Directive, TemplateRef, Inject, Optional} from '@angular/core'; +import {MAT_EXPANSION_PANEL, MatExpansionPanelBase} from './expansion-panel-base'; /** * Expansion panel content that will be rendered lazily @@ -16,5 +17,7 @@ import {Directive, TemplateRef} from '@angular/core'; selector: 'ng-template[matExpansionPanelContent]' }) export class MatExpansionPanelContent { - constructor(public _template: TemplateRef) {} + constructor( + public _template: TemplateRef, + @Inject(MAT_EXPANSION_PANEL) @Optional() public _expansionPanel?: MatExpansionPanelBase) {} } diff --git a/src/material/expansion/expansion-panel.ts b/src/material/expansion/expansion-panel.ts index b26f0a6f26ff..29f67aa1ee83 100644 --- a/src/material/expansion/expansion-panel.ts +++ b/src/material/expansion/expansion-panel.ts @@ -40,6 +40,7 @@ import {filter, startWith, take, distinctUntilChanged} from 'rxjs/operators'; import {matExpansionAnimations} from './expansion-animations'; import {MatExpansionPanelContent} from './expansion-panel-content'; import {MAT_ACCORDION, MatAccordionBase, MatAccordionTogglePosition} from './accordion-base'; +import {MAT_EXPANSION_PANEL} from './expansion-panel-base'; /** MatExpansionPanel's states. */ export type MatExpansionPanelState = 'expanded' | 'collapsed'; @@ -90,6 +91,7 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS = // Provide MatAccordion as undefined to prevent nested expansion panels from registering // to the same accordion. {provide: MAT_ACCORDION, useValue: undefined}, + {provide: MAT_EXPANSION_PANEL, useExisting: MatExpansionPanel} ], host: { 'class': 'mat-expansion-panel', @@ -197,7 +199,7 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI } ngAfterContentInit() { - if (this._lazyContent) { + if (this._lazyContent && this._lazyContent._expansionPanel === this) { // Render the content as soon as the panel becomes open. this.opened.pipe( startWith(null!), diff --git a/src/material/expansion/expansion.spec.ts b/src/material/expansion/expansion.spec.ts index 7b0dbd34a58b..d3dd5862e9f2 100644 --- a/src/material/expansion/expansion.spec.ts +++ b/src/material/expansion/expansion.spec.ts @@ -25,6 +25,7 @@ describe('MatExpansionPanel', () => { PanelWithCustomMargin, LazyPanelWithContent, LazyPanelOpenOnLoad, + NestedLazyPanelWithContent, PanelWithTwoWayBinding, ], }); @@ -70,6 +71,28 @@ describe('MatExpansionPanel', () => { .toContain('Some content', 'Expected content to be rendered.'); })); + it('should not render lazy content from a child panel inside the parent', fakeAsync(() => { + const fixture = TestBed.createComponent(NestedLazyPanelWithContent); + fixture.componentInstance.parentExpanded = true; + fixture.detectChanges(); + + const parentContent: HTMLElement = + fixture.nativeElement.querySelector('.parent-panel .mat-expansion-panel-content'); + const childContent: HTMLElement = + fixture.nativeElement.querySelector('.child-panel .mat-expansion-panel-content'); + + expect(parentContent.textContent!.trim()) + .toBe('Parent content', 'Expected only parent content to be rendered.'); + expect(childContent.textContent!.trim()) + .toBe('', 'Expected child content element to be empty.'); + + fixture.componentInstance.childExpanded = true; + fixture.detectChanges(); + + expect(childContent.textContent!.trim()) + .toBe('Child content', 'Expected child content element to be rendered.'); + })); + it('emit correct events for change in panel expanded state', () => { const fixture = TestBed.createComponent(PanelWithContent); fixture.componentInstance.expanded = true; @@ -490,3 +513,21 @@ class LazyPanelOpenOnLoad {} class PanelWithTwoWayBinding { expanded = false; } + + +@Component({ + template: ` + + Parent content + + + Child content + + + ` +}) +class NestedLazyPanelWithContent { + parentExpanded = false; + childExpanded = false; +} + diff --git a/tools/public_api_guard/material/expansion.d.ts b/tools/public_api_guard/material/expansion.d.ts index 431271a94520..e43c873c6c74 100644 --- a/tools/public_api_guard/material/expansion.d.ts +++ b/tools/public_api_guard/material/expansion.d.ts @@ -61,8 +61,9 @@ export declare class MatExpansionPanelActionRow { } export declare class MatExpansionPanelContent { + _expansionPanel?: MatExpansionPanelBase | undefined; _template: TemplateRef; - constructor(_template: TemplateRef); + constructor(_template: TemplateRef, _expansionPanel?: MatExpansionPanelBase | undefined); } export interface MatExpansionPanelDefaultOptions {