Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using mat-accordion and mat-expansion-panel in ngFor

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.

like image 393
lostintranslation Avatar asked Feb 28 '20 18:02

lostintranslation


2 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 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;
  }
}
like image 179
Thierry Falvo Avatar answered Nov 11 '22 12:11

Thierry Falvo


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

like image 40
TotallyNewb Avatar answered Nov 11 '22 12:11

TotallyNewb