From d224dcf692b93f8fe8bec82aad6984dd8879afef Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sat, 21 Sep 2019 14:57:53 +0200 Subject: [PATCH] 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. --- .../expansion/expansion-panel-base.ts | 25 +++++++++++ .../expansion/expansion-panel-content.ts | 7 +++- src/material/expansion/expansion-panel.ts | 4 +- src/material/expansion/expansion.spec.ts | 41 +++++++++++++++++++ .../public_api_guard/material/expansion.d.ts | 3 +- 5 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 src/material/expansion/expansion-panel-base.ts 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 {