Search code examples
angulartypescriptangular-materialmat-table

Need to reassign data, paginator, and sort on every new row in mat-table


Ive got a <mat-table> where I've implemented an addRow() function that creates a new row in the table using a set of default values based on the data-type.

The issue that I'm having is that everytime I create a new row, I need to re-assign the MatTableDataSource, the MatPaginator, and the MatSort. If I don't perform the reassignments, then the new data doesn't show, or the pagination/sorting does not work.

All these reassignments seem expensive and roundabout, especially on larger datasets, but I'm not entirely sure how to do it better.

dynamic-table.component.ts

import { Component, OnInit, ViewChild } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { TableField } from 'src/app/models/table-field';

const fields: TableField[] = [
  { name: "job", type: "string", editType: "free", options: [] },
  { name: "wage", type: "string", editType: "free", options: [] },
  { name: "state", type: "string", editType: "spinner", options: ["MI", "CA", "TX"] },
  { name: "isUnion", type: "boolean", editType: "checkbox", options: [] }
];

const rows: any[] = [
  { job: "J1", wage: "10.00", state: "MI", isUnion: false },
  { job: "J2", wage: "15.00", state: "TX", isUnion: true },
  { job: "J3", wage: "77.00", state: "CA", isUnion: true }
];

@Component({
  selector: 'app-dynamic-table',
  templateUrl: './dynamic-table.component.html',
  styleUrls: ['./dynamic-table.component.css']
})
export class DynamicTableComponent implements OnInit {
  @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
  @ViewChild(MatSort, { static: true }) sort: MatSort;

  Fields: {}[];
  Rows: {}[];
  ColumnHeaders: string[];
  dataSource: MatTableDataSource<{}>;
  constructor() { }

  ngOnInit() {
    this.Fields = fields.map(field => field);
    this.ColumnHeaders = this.Fields.map(field => field["name"]);
    this.Rows = rows.map(row => row);
    console.log(this.Fields);
    console.log(this.Rows);
    console.log(this.ColumnHeaders);
    this.dataSource = new MatTableDataSource(rows);
  }

  ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }

  addRow() {
    let row = {};
    fields.forEach(field => {
      switch(field.editType) { 
        case "free": { 
           row[field.name] = "default"
           break; 
        } 
        case "spinner": { 
           row[field.name] = field.options[0]; 
           break; 
        }
        case "checkbox": { 
          row[field.name] = false;
          break; 
       }
      } 
    });
    this.Rows = this.Rows.concat(row);
    this.dataSource = new MatTableDataSource(this.Rows);
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }

  printRows() {
    console.log(this.dataSource.data)
  }
}

dynamic-table.component.html

<table mat-table [dataSource]="dataSource" matSort>
    <ng-container *ngFor="let field of Fields" matColumnDef="{{field.name}}">
        <th mat-header-cell *matHeaderCellDef mat-sort-header>{{field.name}}</th>
        <td mat-cell *matCellDef="let row">
            <input *ngIf="field.editType == 'free'" matInput [(ngModel)]="row[field.name]" placeholder="{{row[field.name]}}" required>
            <mat-select *ngIf="field.editType == 'spinner'" [(ngModel)]="row[field.name]">
                <mat-option *ngFor="let option of field.options" [value]="option">{{option}}</mat-option>
            </mat-select>
            <mat-checkbox *ngIf="field.editType == 'checkbox'" [(ngModel)]="row[field.name]">
            </mat-checkbox>
        </td>
    </ng-container>

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

<mat-paginator #paginator [pageSizeOptions]="[10, 20, 30]" showFirstLastButtons></mat-paginator>
<button mat-button color="primary" (click)="addRow()">Add Row</button>
<button mat-button color="primary" (click)="printRows()">Print Rows</button>

Solution

  • You can use setters for pagination and sort components

    @ViewChild(MatPaginator, { static: false })
      set paginator(value: MatPaginator) {
        if(this.dataSource) {
          this.dataSource.paginator = value;
        }
      }
      @ViewChild(MatSort, { static: false }) 
      set sort(value: MatSort) {
        if(this.dataSource) {
          this.dataSource.sort = value;
        }
      }
    

    You can remove the assignation here:

      ngAfterViewInit() {
        //this.dataSource.paginator = this.paginator;
        //this.dataSource.sort = this.sort;
      }
    

    To modify the table you have no choice but to reassign the data source. When you will add a new row, just modify the datasource. Thanks to the setters, the paginator and sort component will be updated.

      addRow() {
        let row = {};
        fields.forEach(field => {
          switch(field.editType) { 
            case "free": { 
               row[field.name] = "default"
               break; 
            } 
            case "spinner": { 
               row[field.name] = field.options[0]; 
               break; 
            }
            case "checkbox": { 
              row[field.name] = false;
              break; 
           }
          } 
        });
        this.Rows = this.Rows.concat(row);
        this.dataSource.data = this.Rows;
      }