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.
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 class="custom-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;
}
}
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: https://stackblitz.com/edit/angular-pvse7t
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With