I am trying to loop some items with an ngFor and create a mat-expansion-panel for each item.

I would like to encapsulate the logic for each item into an item component:

 <item></item> 

Initial attempt:

<mat-accordion> <div *ngFor="let item of items" > <item [item]="item"></item> </div> </mat-accordion> 

Where item template is:

 <mat-expansion-panel> <mat-expansion-panel-header> Header Content </mat-expansion-panel-header> <div> Expandable content </div> </mat-expansion-panel> 

The problem with this approach is that there is an extra html element for my component between <mat-accordion> and <mat-expansion-panel>, which messes up the css for the accordion.

Is there a way that my component can provide the header and content?

 <mat-accordion> <div *ngFor="let item of items" > <mat-expansion-panel> <mat-expansion-panel-header> <!-- put item component header here-> </mat-expansion-panel-header> <div> <!-- put item component content here-> </div> </mat-expansion-panel> </div> </mat-accordion> 

I have read up on ng-content, but I think that is backwards from what I want, I don't want to shove custom content into my component, I want to extract elements from my component and have them render in the parent.

I do realize I could create 2 components, item-header and item-content. However I would really like to keep that login in one component.

12

5 Answers

I encountered the same problem. A Pull Request is opened on this subject but never merged. We can improve a little the layout (border radius), by adding this code below. To keep things simple, best we can do is to restore border-radius on top and bottom of first and last panel.

But it's more difficult for bottom of previous panel not expanded, and top of next panel not expanded...

main component.html :

<mat-accordion> <item *ngFor="let item of items" [item]="item"></item> </mat-accordion> 

item.component.ts:

@Component({ selector: 'app-item', template: ` <mat-expansion-panel> <mat-expansion-panel-header> Header Content </mat-expansion-panel-header> <div> Expandable content </div> </mat-expansion-panel> `, host: { 'class': 'expansion-panel-wrapper', '[class.expanded]': 'expansionPanel && expansionPanel.expanded', } }) export class ItemComponent { @ViewChild(MatExpansionPanel, { static: false }) expansionPanel; ... } 

in styles.scss :

.custom-accordion.mat-accordion { .expansion-panel-wrapper:first-of-type .mat-expansion-panel { border-top-right-radius: 4px; border-top-left-radius: 4px; } & > :not(.expanded) .mat-expansion-panel { border-radius: 0; } & > .expansion-panel-wrapper:last-of-type > .mat-expansion-panel { border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; } } 
3

You were almost there. You don't need to have any wrapping element - you can simply use *ngFor directly on your component:

<mat-accordion> <item *ngFor="let item of items" [item]="item"></item> </mat-accordion> 

Working stack-blitz with simple example of working code:

1

By looking at the code, I think your problem comes from this:

<mat-accordion> <div *ngFor="let item of items" > **//this right here creates a 'div' element for each loop** <item [item]="item"></item> </div> </mat-accordion> 

Try replacing the div with ng-container instead:

<mat-accordion> <ng-container *ngFor="let item of items" > **//ng-container is never rendered in the DOM** <item [item]="item"></item> </ng-container> </mat-accordion> 

From , try

<mat-accordion> <ng-container *ngFor="let item of items" > <mat-expansion-panel item [item]="item"></mat-expansion-panel> </ng-container> </mat-accordion> 

With ItemComponent selector as '[item]'

2

Why not apply ngFor to the expansion panel directly?

<mat-accordion> <mat-expansion-panel *ngFor="let item of items"> <mat-expansion-panel-header> <mat-panel-title> Header Content </mat-panel-title> </mat-expansion-panel-header> <ng-template matExpansionPanelContent> Expandable Content </ng-template> </mat-expansion-panel> </mat-accordion> 

I was trying to share a link from Stackblitz, but unfortunately it seems we have a bug there with sharing, so I'm going to share a screenshot of what I mean:

enter image description here

Edit: it seems Stackblitz decided to cooperate now. Here is the link:

2

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.