javascriptangularreduceangular-material-tabletablecolumn

Angular material table multiple header rows mapping


There is a sample of data table which consists of multiple header rows

|          |  Song 1        | Song 2         | Song 3         |
|---------------------------|----------------|----------------|
|  Artist  | Perc % | Val $ | Perc % | Val $ | Perc % | Val $ |
|-------------------------------------------------------------|
| Q1       | 10%    | 200$  | 15%    | 250$  |        |       |  
| BW       | 10%    | 200$  | 10%    | 200$  |        |       |
| POD      |  5%    | 150$  | 10$    | 200$  |        |       |
|          |        |       |        |       |        |       |
| SUM      | 25%    | 550$  | 25%    | 650$  |        |       |


with provided dataset

{
    "data": [{
            "artistId": "A001",
            "artistName": "Q1",
            "songs": [{
                    "songId": "S001",
                    "songName": "Song 1",
                    "percentage": "10%",
                    "value": "$200"
                },
                {
                    "songId": "S002",
                    "songName": "Song 2",
                    "percentage": "15%",
                    "value": "$250"
                }
            ]
        },
        {
            "artistId": "A002",
            "artistName": "BW",
            "songs": [{
                    "songId": "S001",
                    "songName": "Song 1",
                    "percentage": "10%",
                    "value": "$200"
                },
                {
                    "songId": "S002",
                    "songName": "Song 2",
                    "percentage": "10%",
                    "value": "$200"
                }
            ]
        },
        {
            "artistId": "A003",
            "artistName": "POD",
            "songs": [{
                    "songId": "S001",
                    "songName": "Song 1",
                    "percentage": "5%",
                    "value": "$150"
                },
                {
                    "songId": "S002",
                    "songName": "Song 2",
                    "percentage": "10%",
                    "value": "$200"
                }
            ]
        }
    ],
    "summary": [{
            "songName": "Song 1",
            "totalPercentage": "25%",
            "totalValue": "$550"
        },
        {
            "songName": "Song 2",
            "totalPercentage": "25%",
            "totalValue": "$650"
        }
    ]
}

I'm wondering could provided dataset be mapped in order to get something like on the sample table using angular material table? There is no need for pagination.


Solution

  • You should transform data by combining multiple rows into a single row by artist.

    The transformed data should be looked as below:

    [
        {
            "artistName": "Q1",
            "percentage_0": "10%",
            "value_0": "$200",
            "percentage_1": "15%",
            "value_1": "$250"
        },
        ...,
        {
            "artistName": "SUM",
            "percentage_0": "25%",
            "value_0": "$550",
            "percentage_1": "25%",
            "value_1": "$650"
        }
    ]
    
    displayedColumns: string[] = ['artistName'];
    
    firstHeaderColumns = ['_'];
    
    transformDataSource: any[] = [];
    
    ngOnInit() {
      let songs = this.data.summary.map((x) => x.songName);
      this.firstHeaderColumns = this.firstHeaderColumns.concat(songs);
      this.displayedColumns = this.displayedColumns.concat(
        ...songs.map((x: string, i: number) => [`percentage_${i}`, `value_${i}`])
      );
    
      this.transformDataSource= this.data.data.map((x: any) => {
        let obj: any = { artistName: x.artistName };
    
        for (let i = 0; i < songs.length; i++) {
          obj[`percentage_${i}`] = x.songs[i].percentage;
          obj[`value_${i}`] = x.songs[i].value;
        }
    
        return obj;
      });
    
      let summary: any = { artistName: 'SUM' };
      this.data.summary.forEach((x: any, i: number) => {
        summary[`percentage_${i}`] = x.totalPercentage;
        summary[`value_${i}`] = x.totalValue;
      });
      this.transformDataSource.push(summary);
    }
    
    <table mat-table [dataSource]="transformDataSource" class="mat-elevation-z8">
      <ng-container *ngFor="let column of displayedColumns">
        <ng-container matColumnDef="{{column}}">
          <th mat-header-cell *matHeaderCellDef>
            <ng-container
              [ngTemplateOutlet]="headerTemplate"
              [ngTemplateOutletContext]="{ $implicit: column }"
            >
            </ng-container>
          </th>
          <td mat-cell *matCellDef="let element">{{element[column]}}</td>
        </ng-container>
      </ng-container>
    
      <ng-container *ngFor="let headerColumn of firstHeaderColumns">
        <ng-container matColumnDef="{{headerColumn}}">
          <th
            mat-header-cell
            *matHeaderCellDef
            [attr.colspan]="headerColumn != '_' ? 2 : 1"
            [style.text-align]="'center'"
          >
            {{headerColumn != '_' ? headerColumn : '' }}
          </th>
        </ng-container>
      </ng-container>
    
      <tr mat-header-row *matHeaderRowDef="firstHeaderColumns"></tr>
      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
    </table>
    
    <ng-template let-prop #headerTemplate>
      <ng-container [ngSwitch]="prop.split('_')[0]" [style.text-align]="'center'">
        <ng-container *ngSwitchCase="'artistName'">Artist</ng-container>
        <ng-container *ngSwitchCase="'percentage'">Perc %</ng-container>
        <ng-container *ngSwitchCase="'value'">Value %</ng-container>
      </ng-container>
    </ng-template>
    

    Demo @ StackBlitz