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.
125 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; } } 3You 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:
1By 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> <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]'
2Why 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:
Edit: it seems Stackblitz decided to cooperate now. Here is the link:
2
