4

I'm having trouble finding an answer on how to merge columns cells in mat-table. Only saw few examples in merging row cells using when. So was wondering if i can find answers here.

I have this JSON data:

{
    "id": 4,
    "description": "this is another block",
    "block_size": 3480,
    "lot_count": 5,
    "lots": [
        {
            "id": 17,
            "lot_name": "test 17",
            "status": "SOLD",
            "block_id": 4,
            "lot_id": 1,
            "lot_size": 828
        },
        {
            "id": 18,
            "lot_name": "test 18",
            "status": "OPEN",
            "block_id": 4,
            "lot_id": 2,
            "lot_size": 885
        },
        {
            "id": 19,
            "lot_name": "test 19",
            "status": "SOLD",
            "block_id": 4,
            "lot_id": 3,
            "lot_size": 648
        },
        {
            "id": 20,
            "lot_name": "test 20",
            "status": "OPEN",
            "block_id": 4,
            "lot_id": 4,
            "lot_size": 553
        },
        {
            "id": 21,
            "lot_name": "Test 21",
            "status": "OPEN",
            "block_id": 4,
            "lot_id": 5,
            "lot_size": 566
        }
    ]
}

And was expecting an output in mat-table as:

+------------------------------------------------------------------+
| No.        Lot Name    Block    Block Size    Lot Id    Lot Size |
+------------------------------------------------------------------+
| 17         test 17                            1         828      |
| 18         test 18                            2         885      |
| 19         test 19     4        3480          3         648      |
| 20         test 20                            4         553      |
| 21         test 21                            5         566      |
+------------------------------------------------------------------+

As you can see I want to make the cells in columns Block and Block Size to merge.

Akber Iqbal
  • 12,257
  • 11
  • 34
  • 52
simple guy
  • 541
  • 4
  • 15

2 Answers2

8

Meanwhile you probably found a solution to your problem but since I just created the following StackBlitz, I share it in case someone else looks for a solution to the same issue.

https://stackblitz.com/edit/angular-gevqvq

The RowSpanComputer class from this solution is based on code found in the open source project Koia. In there, the computed rowspan attributes are used within summary-table.component.html.

UPDATE

The code example in the above linked StackBlitz works only for optimistic cases. Ideally no row spans are computed for the last table column, otherwise rows are not displayed if they are identical to the previous one.

ngOnInit() {
  this.columnNames = Object.keys(this.data[0]);
  this.lastColumnName = this.columnNames[this.columnNames.length - 1];
  this.allButLastColumnNames = this.columnNames.slice(0, -1);
  this.rowSpans = this.rowSpanComputer.compute(this.data, this.allButLastColumnNames);
}

In the HTML template this must also be taken into account. There we treat the preceding and the last column differently.

<table mat-table *ngIf="rowSpans" [dataSource]="data" class="mat-elevation-z8">
  <ng-container *ngFor="let columnName of allButLastColumnNames; let iCol = index" [matColumnDef]="columnName">
    <th mat-header-cell *matHeaderCellDef>{{ columnName }}</th>
    <td mat-cell *matCellDef="let row; let iRow = index" [attr.rowspan]="rowSpans[iCol][iRow].span"
       [style.display]="rowSpans[iCol][iRow].span === 0 ? 'none'  : ''">{{ row[columnName] }}</td>
  </ng-container>
  <ng-container [matColumnDef]="lastColumnName">
    <th mat-header-cell *matHeaderCellDef>{{ lastColumnName }}</th>
    <td mat-cell *matCellDef="let row; let iRow = index">{{ row[lastColumnName] }}</td>
  </ng-container>
  <tr mat-header-row *matHeaderRowDef="columnNames"></tr>
  <tr mat-row *matRowDef="let row; columns: columnNames"></tr>
</table>

Please take a look at this improved StackBlitz and see how it works.

uminder
  • 14,658
  • 3
  • 20
  • 45
  • Amazing!!! this saved a ton of work for me, thanks a million – shahnshah Sep 13 '20 at 08:58
  • Thanks for it, works like a charm. I noticed, though, that it works correctly for first 3 columns, all further columns get spanColumnContext.spannnedRow = undefined. Perhaps you know why this is? – robs23 Oct 17 '20 at 13:33
  • @robs23: I had a look at the code I posted and noticed there was a problem. I updated/improved my answer accordingly. – uminder Oct 18 '20 at 17:20
  • You probably were targetting some other edge case, for me things didn't change a bit - it still doesn't work. I forked your example on https://stackblitz.com/edit/angular-table-row-span-gn6rnu?file=src/app/row-span.component.ts . If you have a chance, please check it out, I changed your data to the exact data I work with and no rows are merged – robs23 Oct 19 '20 at 11:40
  • @robs23: The `RowSpanComputer` class computes the `rowspan` for each cell out of the specified table data (array of rows). It basically loops over the rows and increments the `rowspan` for cells as long as their value remains unchanged and **left located cells were also spanned**. As soon as the value changes, the corresponding `rowspan` is reset to zero. I took a quick look to your code and see that the values in the first few columns are different for each row. Therefore no `rowspan` is applied at all. If you need a solution for your particular case, please post a new question. – uminder Oct 19 '20 at 14:19
  • Thanks. What I didn't catch was the fact that you have to provide columns ordered by rowspan descending.. I mean, If first column does not contain repeatable data (all values are unique in first column for all rows), rowspan will always be 1 for all next columns, regardless if there are any repeatable values there or not.. If I feed your function with column names in appropriate order, it will count rowspan correctly. And I can reorder them the way I want in the template anyway – robs23 Oct 20 '20 at 11:06
0

You wanted to "make the cells in columns Block and Block Size to merge."... there is no field 'Block Size', so i assume that you meant 'Lot Size' to merge with 'Block ID'.

For this we create an arbitary column name 'mergedField' (in TS) and then print out {{element.block_id}}{{element.lot_size}} together (in the HTML)

relevant TS:

import { Component } from '@angular/core';
import { AppService } from './app.service'

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  name = 'Angular';
  displayedColumns: string[] = ['id', 'lot_name', 'block_id', 'mergedField', 'lot_id', 'lot_size', 'status'];
  dataSource;
  myJSONData: any[] = [];
  constructor(private userService: AppService) {
  }

  ngOnInit() {
    let someVar = this.userService.getJSON();
    this.myJSONData = someVar.lots;
    console.log(this.myJSONData);
    this.dataSource = this.myJSONData;
  }

}

relevant HTML:

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

  <!--- Note that these columns can be defined in any order.
        The actual rendered columns are set as a property on the row definition" -->

  <!-- id Column -->
  <ng-container matColumnDef="id">
    <th mat-header-cell *matHeaderCellDef> No </th>
    <td mat-cell *matCellDef="let element"> {{element.id}} </td>
  </ng-container>

  <!-- block_id Column -->
  <ng-container matColumnDef="block_id">
    <th mat-header-cell *matHeaderCellDef> Block </th>
    <td mat-cell *matCellDef="let element"> {{element.block_id}} </td>
  </ng-container>

  <!-- block_id Column -->
  <ng-container matColumnDef="mergedField">
    <th mat-header-cell *matHeaderCellDef> Block Size (merged) </th>
    <td mat-cell *matCellDef="let element"> {{element.block_id}}{{element.lot_size}} </td>
  </ng-container>


  <!-- lot_id Column -->
  <ng-container matColumnDef="lot_id">
    <th mat-header-cell *matHeaderCellDef> Lot ID </th>
    <td mat-cell *matCellDef="let element"> {{element.lot_id}} </td>
  </ng-container>

  <!-- lot_name Column -->
  <ng-container matColumnDef="lot_name">
    <th mat-header-cell *matHeaderCellDef> Lot Name </th>
    <td mat-cell *matCellDef="let element"> {{element.lot_name}} </td>
  </ng-container>

  <!-- lot_size Column -->
  <ng-container matColumnDef="lot_size">
    <th mat-header-cell *matHeaderCellDef> Lot Size </th>
    <td mat-cell *matCellDef="let element"> {{element.lot_size}} </td>
  </ng-container>


  <!-- status Column -->
  <ng-container matColumnDef="status">
    <th mat-header-cell *matHeaderCellDef> Status </th>
    <td mat-cell *matCellDef="let element"> {{element.status}} </td>
  </ng-container>


  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

complete working stackblitz here

Akber Iqbal
  • 12,257
  • 11
  • 34
  • 52