3

Trying to use the mat-table directives, such as *matRowDef and multiTemplateDataRows to build a table where each row can have a varying number of sub-rows.

An example interface would look like this:

interface ReportCard {
  student: string;
  reports: Report[];
}

interface Report {
  class: string;
  teacher: string;
  grade: string;
}

...where there is no fixed number of reports.

I can easily build a table with *ngFor, but I would like to use the built-in Material Table directives.

<table>
  <thead>
    <tr>
      <th>student</th>
      <th colspan="3">
        classes
      </th>
    </tr>
    <tr>
      <th></th>
      <th>name</th>
      <th>teacher</th>
      <th>grade</th>
    </tr>
  </thead>
  <tbody>
    <ng-container *ngFor="let reportCard of reportCards">
      <tr
        *ngFor="let report of reportCard.reports; let first = first"
      >
        <td
          *ngIf="first"
          [attr.rowspan]="reportCard.reports.length"
        >
          {{ reportCard.student }}
        </td>
        <td>{{ report.class }}</td>
        <td>{{ report.teacher }}</td>
        <td>{{ report.grade }}</td>
      </tr>
    </ng-container>
  </tbody>
</table>

I think I can also create an arbitrarily high number of templates for data rows using multiTemplateDataRows to handle each report (similar to how it is done in this answer), but that does not seem like the best solution. Also I'm not sure I am able to access the correct index for each report while trying to populate the <td> cell with the correct data.

Stackblitz of table I am trying to build

Any help appreciated.

ahong
  • 1,041
  • 2
  • 10
  • 22

1 Answers1

1

I suggest transforming your data to a flat array holding both the report-card and its reports before using it as a data-source. In your particular case this will be the easiest and most understandable.

@Component({
  selector: 'table-basic-example',
  styleUrls: ['table-basic-example.css'],
  templateUrl: 'table-basic-example.html',
})
export class TableBasicExample implements OnInit {
  
  private reportCards = REPORT_DATA;
  
  public reportCardColumns: string[] = ['reportCard.student'];
  public reportColumns: string[] = ['report.class', 'report.grade', 'report.teacher'];
  public dataSource: Array<ReportCard|Report>;
  

  public ngOnInit(): void {
    const data: Array<ReportCard|Report> = [];
    for (let reportCard of REPORT_DATA) {
      data.push(reportCard, ...reportCard.reports);
    }
    this.dataSource = data; 
  }

  public isReportCard(index: number, data: ReportCard|Report): boolean {
    return Reflect.has(data, 'reports');
  }
  
  public isReport(index: number, data: ReportCard|Report): boolean {
    return !Reflect.has(data, 'reports');
  }
}

In your template you can now simply use two when predicates with their own columns and css classes:

<tr mat-header-row *matHeaderRowDef="reportColumns" ></tr>
<tr mat-row *matRowDef="let row; columns: reportCardColumns; when: isReportCard" class="reportCard"></tr>
<tr mat-row *matRowDef="let row; columns: reportColumns; when: isReport" class="report"></tr>

You can find a working StackBlitz here.

Update:

After additional comments here another Stackblitz that exactly does what was required in the original question.

Wilt
  • 41,477
  • 12
  • 152
  • 203
  • thanks for the answer and stackblitz... if we want the student name to also be a column and instead of multi-colspan row would you suggest the same solution? I'm guess we would have to transform the data differently and might be messier. – ahong Mar 31 '23 at 23:41
  • @ahong You could solve that for example by introducing an `empty` column, use it where you don't have data in `reportCardColumns` and `reportColumns` and then define `headerColumns` separately. See example [here in this fork of my previous Stackblitz](https://stackblitz.com/edit/angular-ibcmee-isejxv?file=src/app/table-basic-example.html) – Wilt Apr 01 '23 at 18:55
  • I see, I think I know what you mean, but I probably would have to structure the data differently... since I would like to have a table cell that is multi-rowspan instead of multi-colspan, similar to my stackblitz – ahong Apr 04 '23 at 02:31
  • 1
    OK, now I saw that rowspan in your example. Wasn't clear from the question. [Here another Stackblitz](https://stackblitz.com/edit/angular-ibcmee-3a2oux?file=src%2Fapp%2Ftable-basic-example.css,src%2Fapp%2Ftable-basic-example.ts,src%2Fapp%2Ftable-basic-example.html) with how you can easily do that. – Wilt Apr 04 '23 at 06:24
  • Awesome, yea I guess splicing the data is the best way to do it. – ahong Apr 04 '23 at 22:33