Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Table] Add example with dynamic columns #5927

Closed
rafaelss95 opened this issue Jul 21, 2017 · 59 comments · Fixed by #22811
Closed

[Table] Add example with dynamic columns #5927

rafaelss95 opened this issue Jul 21, 2017 · 59 comments · Fixed by #22811
Assignees
Labels
area: material/table docs This issue is related to documentation P4 A relatively minor issue that is not relevant to core functions

Comments

@rafaelss95
Copy link
Contributor

Bug, feature request, or proposal:

Proposal

What is the expected behavior?

It'd be nice to have example(s) on how to use md-table with dynamic columns.

What is the current behavior?

Currently all the examples are with hard coded columns, like this:

<!-- ID Column -->
<ng-container cdkColumnDef="userId">
  <md-header-cell *cdkHeaderCellDef md-sort-header> ID </md-header-cell>
  <md-cell *cdkCellDef="let row"> {{row.id}} </md-cell>
</ng-container>

<!-- Progress Column -->
<ng-container cdkColumnDef="progress">
  <md-header-cell *cdkHeaderCellDef md-sort-header> Progress </md-header-cell>
  <md-cell *cdkCellDef="let row"> {{row.progress}}% </md-cell>
</ng-container>

@andrewseguin

@willshowell
Copy link
Contributor

willshowell commented Jul 21, 2017

For those interested, here was my approach

https://plnkr.co/edit/UntMipJO7lQVFCCSq3sZ?p=preview

<!-- Generic column definition -->
<ng-container *ngFor="let column of columns" [cdkColumnDef]="column.columnDef">
  <md-header-cell *cdkHeaderCellDef>{{ column.header }}</md-header-cell>
  <md-cell *cdkCellDef="let row">{{ column.cell(row) }}</md-cell>
</ng-container>
/** Table columns */
columns = [
  { columnDef: 'userId',    header: 'ID',       cell: (row: UserData) => `${row.id}`        },
  { columnDef: 'userName',  header: 'Name',     cell: (row: UserData) => `${row.name}`      },
  { columnDef: 'progress',  header: 'Progress', cell: (row: UserData) => `${row.progress}%` }
];

/** Column definitions in order */
displayedColumns = this.columns.map(x => x.columnDef);

EDIT: Here is a (hopefully) evergreen stackblitz with the exact same approach

https://stackblitz.com/edit/material2-beta11-vmwjpe

@andrewseguin andrewseguin self-assigned this Jul 21, 2017
@alexrun
Copy link

alexrun commented Jul 30, 2017

Is it possible to internationalize these dynamic columns implementation?

@pueaau
Copy link

pueaau commented Aug 9, 2017

I wonder what you refer to when you say "dynamic"? Many things regarding columns can be dynamic.
If your goal is to dynamically change which columns are shown then I think the simplest approach is to change displayedColumns.

@sophistyx
Copy link

I think he outlined it pretty well here:

//-----
What is the current behavior?
Currently all the examples are with hard coded columns, like this:
//-----

What he (and myself) are looking for is the ability to render an arbitrary number of columns with heading names taken from some underlying datasource, e.g. columns: ColumnDefinition[];

It has to be said that the current mechanism for binding md-table to a dataSource is dreadful. It should be consistent with how we *ngFor over collections to render results, only in this instance, stamp out column definitions. Same story for rows.

@eltimmo
Copy link

eltimmo commented Oct 5, 2017

Hi @willshowell. Thanks, just looking at how you approached this. I'm not sure why but the Plunker you created has stopped working.

@willshowell
Copy link
Contributor

@eltimmo updated the original comment!

@andrewseguin andrewseguin added docs This issue is related to documentation P4 A relatively minor issue that is not relevant to core functions labels Oct 19, 2017
@jscharett
Copy link

Can't seem to get this working in beta 12. Always get error that cdk-table can't find column with id.

Also, if I am using the MatTableModule, do I need to pull in the CdkTableModule?

@jscharett
Copy link

It seems that there is an issue with using dynamic columns inside the ng-bootstrap modal. As neither the ngx-bootstrap modal and the material2 modal seem to work when lazy loaded, I'm using ng-bootstrap. It seems that the ContentChildren don't include column defs when using *ngFor.

@jscharett
Copy link

Well, looks like I am wrong. I was able to create this Stackblitz and it works, so not sure why its failing for me locally. Ugh

@jscharett
Copy link

PEBCAK

@eltimmo
Copy link

eltimmo commented Oct 30, 2017

From Will's example, I created this. I'm looking forward to simple tables being implemented :-)
https://stackblitz.com/edit/angular-dynamic-tables

@Bsujeet
Copy link

Bsujeet commented Nov 6, 2017

Hi,
I was looking for how to write generic code to implement the pagination,filtering and sorting.
Thanks

@donmccurdy
Copy link

donmccurdy commented Dec 8, 2017

Has this support for dynamic columns made it into a stable release? I'm trying to implement this and I'm not sure whether I'm doing something wrong, or whether my use case just isn't supported. I'd like to load arbitrary data from a CSV, detect column headers at runtime, and show that in a table.

<mat-table *ngIf="data" [dataSource]="data">
  <ng-container *ngFor="let column of columns" [matColumnDef]="column">
    <mat-header-cell *matHeaderCellDef>{{ column }}</mat-header-cell>
    <mat-cell *matCellDef="let row">{{ row[column] }}</mat-cell>
  </ng-container>
</mat-table>
const rows = [
  {"id": "1", "name": "Macaw, scarlet", "lat": "31.215291", "lng": "118.931012"},
  {"id": "2", "name": "Armadillo, nine-banded", "lat": "35.663752", "lng": "103.389346"},
  {"id": "3", "name": "Greater roadrunner", "lat": "13.17535", "lng": "44.27461"},
  {"id": "4", "name": "Goanna lizard", "lat": "22.671042", "lng": "113.823131"},
  {"id": "5", "name": "Cape starling", "lat": "16.0213558", "lng": "100.417181"}
];
const columns = Array<string> (Object.keys(rows[0]));
const data = new MatTableDataSource<Object>(rows);

Result:

AppComponent.html:31 ERROR Error: Missing definitions for header and row, cannot determine which columns should be rendered.
    at getTableMissingRowDefsError (table.es5.js:363)
    at MatTable.CdkTable.ngAfterContentInit (table.es5.js:512)
    at callProviderLifecycles (core.js:12422)
    at callElementProvidersLifecycles (core.js:12399)
    at callLifecycleHooksChildrenFirst (core.js:12383)
    at checkAndUpdateView (core.js:13511)
    at callViewAction (core.js:13858)
    at execEmbeddedViewsAction (core.js:13816)
    at checkAndUpdateView (core.js:13509)
    at callViewAction (core.js:13858)

EDIT: The problem was that I needed to include the following, inside of <mat-table/>:

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

@andrewseguin
Copy link
Contributor

@donmccurdy Looks like you're all set except you are missing definitions for <mat-row> and <mat-header-row> where you will define which columns to display

@donmccurdy
Copy link

@andrewseguin thanks! That was exactly it. 🙂

@boombard
Copy link

Hi

Has anyone tried this with sorting enabled?

columnSetup = [
  {
    def: 'name',
    title: 'Name',
    displayFcn: (item: Item) => item.name,
    sort: true
  }
];
<mat-table #table [dataSource]="tableData" matSort>
  <ng-container *ngFor="let column of columnSetup" matColumnDef="{{ column.def }}">
    <mat-header-cell *matHeaderCellDef [attr.mat-sort-header]="column.sort ? column.def : null">{{ column.title }}</mat-header-cell>
    <mat-cell *matCellDef="let item"> {{ column.displayFcn(item) }} </mat-cell>
  </ng-container>
  <mat-header-row *matHeaderRowDef="displayedColumns()"></mat-header-row>
  <mat-row *matRowDef="let row; columns: displayedColumns();"></mat-row>
</mat-table>

When this compiles, mat-sort-header="true" renders correctly in <mat-header-cell>, however the sort functionality doesn't work.

Any suggestions appreciated!

@anywayTsao
Copy link

anywayTsao commented Feb 7, 2018

@boombard , I've tried you template code.
[attr.mat-sort-header]="column.sort ? column.def : null" => not work
[mat-sort-header]="column.sort ? column.def : null"

It looks the binding you use => [attr.mat-sort-header] is bind to the DOM, not a property binding.
https://angular.io/guide/template-syntax#binding-targets

@nothingisnecessary
Copy link

nothingisnecessary commented Mar 9, 2018

@boombard I tried different property bindings and I couldn't get it to work. I found that if you add "disabled" attribute it disables it, but other than using *ngIf I didn't find a way to conditionally emit the disabled attribute.

Conditionally sortable columns (works fine in a dynamic column loop too)

  <div *ngIf="sortable">
    <mat-header-cell *matHeaderCellDef mat-sort-header> No. </mat-header-cell>
  </div>
  <div *ngIf="!sortable">
    <mat-header-cell *matHeaderCellDef mat-sort-header disabled> No. </mat-header-cell>
  </div>

@boombard
Copy link

boombard commented Mar 9, 2018

@nothingisnecessary Thanks for the suggestion, in the end I also went with *ngIf - seems like the only option

@jimmykane
Copy link

Jeee! This is not added to the docs yet?

@jimmykane
Copy link

jimmykane commented May 24, 2018

And here is a more complicated one with sorting, checkboxes and filtering

FYI


<mat-card class="mat-elevation-z4">
  <mat-form-field>
    <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
  </mat-form-field>
  <mat-table *ngIf="data" [dataSource]="data" matSort>
    <ng-container *ngFor="let column of columns; first as isFirst; last as isLast" [matColumnDef]="column">
      <mat-header-cell *matHeaderCellDef mat-sort-header [disabled]="isFirst || isLast">
        <mat-checkbox *ngIf="isFirst" (change)="$event ? masterToggle() : null"
                      [checked]="selection.hasValue() && isAllSelected()"
                      [indeterminate]="selection.hasValue() && !isAllSelected()">
        </mat-checkbox>
        <span *ngIf="!isFirst && !isLast">
            <mat-icon *ngIf="getColumnIcon(column)">{{ getColumnIcon(column) }}</mat-icon>
        </span>
      </mat-header-cell>
      <mat-cell *matCellDef="let row">
        <mat-checkbox *ngIf="column === 'Checkbox'" (click)="$event.stopPropagation()"
                      (change)="$event ? checkBoxClick(row) : null"
                      [checked]="selection.isSelected(row)">
        </mat-checkbox>
        <span *ngIf="column === 'Actions'">
          <app-event-card-actions-menu [event]="row[column]"></app-event-card-actions-menu>
        </span>
        <span *ngIf="column === 'Name'" matTooltip="{{ row[column] }}">
          {{ row[column] | slice:0:10 }}
        </span>
        <span *ngIf="column !== 'Checkbox' && column !== 'Actions' && column !== 'Name'">
          {{ row[column] }}
        </span>
      </mat-cell>
    </ng-container>
    <mat-header-row *matHeaderRowDef="columns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: columns;" [routerLink]="['/eventDetails']"
             [queryParams]="{ eventID: row['Actions'].getID(), tabIndex: 0 }"></mat-row>
  </mat-table>
</mat-card>

@paco76
Copy link

paco76 commented May 25, 2018

Hi jimmykane, thank you!
do you mind also sharing your component logic? for us to have a real full example.

@natecowen
Copy link

@paco76 Here is an example of my code, both the view and the typescript logic. It's based on the example that @jimmykane. I am not using sort, but I do have filtering and pagination working.

In my example, I am using the mat-table as a reusable child component. The parent component passes in certain items. Below is my code that I use to call implement the child component in the parent.

<div *ngIf="results">
  <app-cust-data-table *ngIf="showTableResults"
    [(receivedData)] = "results"
    [(columns)] = "columns"
    tableTitle = "Search Results"
    (clickedItem) = "viewItem($event)"
    (pageEvent) = "updatePagination($event)"
    >
  </app-cust-data-table>
</div>

This is my child component HTML view

<mat-card class="search-results">
    <mat-card-header>
      <mat-card-title >
      <h4>{{tableTitle}}</h4>
      </mat-card-title>
    </mat-card-header>


    <mat-card-content>
        <mat-form-field class="full-width-filter">
            <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter" autocomplete="off">
          </mat-form-field>

      <table mat-table #table [dataSource]="dataSource" style="width:100%">

      <ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
        <div *ngIf="column.columnDef !== 'detailBtn'">
            <th mat-header-cell *matHeaderCellDef > {{column.header}} </th>
            <td td mat-cell *matCellDef="let row">
              <strong> &nbsp;{{ column.dataName(row) }}</strong>
            </td>
        </div>

        <div *ngIf="column.columnDef === 'detailBtn'">
            <th mat-header-cell *matHeaderCellDef > {{column.header}} </th>
            <td td mat-cell *matCellDef="let row">
                <button mat-raised-button color="primary" id="column.dataName(row)" (click)="viewItem(column.dataName(row))">View</button>

            </td>
        </div>

        </ng-container>

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

      <mat-paginator [pageSizeOptions]="[25, 50, 100]" showFirstLastButtons [length]="length" (page)="updateProductsTable($event)"></mat-paginator>
    </mat-card-content>
  </mat-card>

This is the typescript of the child component

import { Component, ViewChild, Input, OnChanges, Output, EventEmitter, OnInit } from '@angular/core';
import { MatPaginator, MatTableDataSource, PageEvent } from '@angular/material';

@Component({
  selector: 'app-cust-data-table',
  templateUrl: 'data-table.component.html',
  styleUrls: ['./data-table.component.scss']
})
export class CustDataTableComponent implements OnChanges, OnInit {
  @Input() displayedColumns: string[];
  @Input() receivedData;
  @Input() tableTitle: string;
  @Input() columns: any[] = [];
  @Input() metaCount: number;

  @Output() clickedItem = new EventEmitter();
  @Output() pageEvent = new EventEmitter<PageEvent>();

  dataSource: MatTableDataSource<any>;
  @ViewChild(MatPaginator) paginator: MatPaginator;

  pageIndex = 0;
  pageSize = 25;
  length;

  ngOnInit() {}

  ngOnChanges() {
    if (this.columns !== undefined || this.columns !== null) {
      if (this.metaCount) {
        this.dataSource = new MatTableDataSource(this.receivedData);
        this.displayedColumns = this.columns.map(x => x.columnDef);

        this.length = this.metaCount;
        this.paginator.length = this.metaCount;

        // IMPORTANT! Do NOT use below if you are getting pagination from API Call. This is only for letting material figure out pagination
        // this.dataSource.paginator = this.paginator;
      } else {
        this.dataSource = new MatTableDataSource(this.receivedData);

        this.displayedColumns = this.columns.map(x => x.columnDef);
        this.dataSource.paginator = this.paginator;

        this.dataSource.paginator.pageSize = this.pageSize;
        this.dataSource.paginator.pageIndex = this.pageIndex;

        this.dataSource.paginator.length = this.receivedData.length;
      }
    }
  }

  applyFilter(filterValue: string) {
    filterValue = filterValue.trim(); // Remove whitespace
    filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches
    this.dataSource.filter = filterValue;
    if (this.dataSource.paginator) {
      this.dataSource.paginator.firstPage();
    }
  }

  updateProductsTable(event: PageEvent) {
    this.pageSize = event.pageSize;
    this.pageIndex = event.pageIndex + 1; // API starts 1, Mat-Table starts at 0

    this.pageEvent.emit(event);
  }

  viewItem(guid) {
    this.clickedItem.emit(guid);
  }
}

A few items to note: I am using the component twice in my parent component. Using mat-tabs I have a search tab that uses can enter in text to search for. Using the other tab, they can view all 3000 items.

The search uses materials built in pagination by setting this.dataSource.paginator = this.paginator. The all products uses pagination set via the server. In my getAllProducts server I pass in a default number of items to return and a default page number to start with. The mat-paginator then fires an event back to the parent component for the call of the next page of items.

Hope this helps. It took me a while to figure out the way to get filtering and pagination to work for both types of request.

@dgreatorex
Copy link

Hi @natecowen,

Any chance you can share a stackblitz?

Thanks! :)
Dave

@fxck
Copy link
Contributor

fxck commented Jul 3, 2018

Is it possible to somehow pass columndefs down from a parent?

@natecowen
Copy link

natecowen commented Jul 3, 2018

@fxck Yes, that is what I am doing in my example. In my parents typescript I declare columns near the top of the parent class.

columns: any[] = [
    { columnDef: 'productName', header: 'Product Name', dataName: row => `${row.name}` },
    { columnDef: 'productDescription', header: 'Description', dataName: row => `${row.itemDescription}` },
    { columnDef: 'detailBtn', header: 'View/Edit', dataName: row => `${row.guid}` }
  ];```

That is then passed into the parents html via [(columns)] = "columns". See example 

```javascript
  <app-cust-data-table *ngIf="showTableResults"
    [(receivedData)] = "results"
    [(columns)] = "columns"
    tableTitle = "Search Results"
    (clickedItem) = "viewItem($event)"
    (pageEvent) = "updatePagination($event)"
    >
  </app-cust-data-table>

@dgreatorex I started working on a stackblitz, but haven't been able to finish it. It's currently not fully working as I need to find an online api for search functionality. However, you can see it working for all products. Click the all products tab to see it in action. Hopefully it helps. Here is the stackblitz

@fxck
Copy link
Contributor

fxck commented Jul 3, 2018

What I mean is to pass down whole templates, imagine you want to use a component inside the cell.

@willshowell
Copy link
Contributor

@fxck CdkTable has addColumnDef and removeColumnDef methods. It's a little clunky to use, but I think it does what you need

https://stackblitz.com/edit/angular-i4dftq?file=app/cdk-table.ts

@fxck
Copy link
Contributor

fxck commented Jul 3, 2018

@willshowell I threw together this https://stackblitz.com/edit/angular-i4dftq-kxqbk4?file=app%2Ftable.ts as what I really need is to be able to control the inside of the cell, depending on the data from the column... it works, but not quite, for example I couldn't think of a way to hide the original text in case a special template is passed down for this particular column..

ideally I'd like to pass down some special directive instead of the ng-template, but the problem is that I don't know how to properly pass data back up to a directive same way as you do with ngTemplateOutletContext

basically something like this (pseudo code)

<vsh-universal-list-table
  [data]="data"
  [columns]="columns"
  [activeColumns]="activeColumns">
  <ng-container 
    #data="vshUniversalListTableCell"
    *vshUniversalListTableCell="email">
    <a href="mailto:">{{ data.element[data.column?.name] }}</a>
  </ng-container>
</vsh-universal-list-table>

in which case the original thing wouldn't show up

<mat-cell *matCellDef="let element">
  <!--  doesn't have special template -->
  <ng-container *ngIf="!templates[column.name]">
    {{ element[column.name] }}
  </ng-container>

  <!-- has special template  -->
  <ng-container *ngIf="templates[column.name]">
    <ng-template
      [ngTemplateOutletContext]="{
        column: column,
        element: element
      }"
      [ngTemplateOutlet]="templates[column.name]">
    </ng-template>
  </ng-container>
</mat-cell>

ugh..

@fxck
Copy link
Contributor

fxck commented Jul 13, 2018

FYI what I wanted to achieve is quite possible, same principle as shown in this article - https://alligator.io/angular/reusable-components-ngtemplateoutlet/

@daiyis
Copy link

daiyis commented Sep 14, 2018

FYI what I wanted to achieve is quite possible, same principle as shown in this article - https://alligator.io/angular/reusable-components-ngtemplateoutlet/

hey @fxck, I am doing something similar to create dynamic column with different templates. how can you pass the templates together to the base component and reference it with something like templates[column.name]?

@relair
Copy link

relair commented Oct 1, 2018

Is it possible to somehow pass columndefs down from a parent?
What I mean is to pass down whole templates, imagine you want to use a component inside the cell.

Yes, I have created a library that does that: https://www.npmjs.com/package/material-dynamic-table
Feel free to take a look - or use the library itself. It works by defining what components do you want to used for your column types and then just providing a definition of your table as an array of your column configurations. It also allows to define individual column filters in the similar way.

@shripalshah
Copy link

shripalshah commented Oct 30, 2018

Bug, feature request, or proposal:

Proposal

What is the expected behavior?

It'd be nice to have example(s) on how to use md-table with dynamic columns.

What is the current behavior?

Currently all the examples are with hard coded columns, like this:

<!-- ID Column -->
<ng-container cdkColumnDef="userId">
  <md-header-cell *cdkHeaderCellDef md-sort-header> ID </md-header-cell>
  <md-cell *cdkCellDef="let row"> {{row.id}} </md-cell>
</ng-container>

<!-- Progress Column -->
<ng-container cdkColumnDef="progress">
  <md-header-cell *cdkHeaderCellDef md-sort-header> Progress </md-header-cell>
  <md-cell *cdkCellDef="let row"> {{row.progress}}% </md-cell>
</ng-container>

@andrewseguin

Try doing <md-cell *cdkCellDef="let row"> {{row[progress]}}
This is not a bug. This was related to concatenating two literals in Interpolation.

Good Luck!!

@nasreddineskandrani
Copy link

nasreddineskandrani commented May 18, 2019

Hi!
@daiyis @fxck
With this solution the columns are dynamic and the cell content also.

So this is a working draft that shows how to pass a component to render it in a cell.
I didn't build a real dumb component but the technic is inside my repo using a directive dynamic-cell.directive.ts ( sorry I have a baby in one hand and i code with the other, you can clean it and extract a wrapper of mat-table for now :p).

Example:
the column 4 in the example is symbol with a custom component to render in the cell.
Github:
https://github.com/nasreddineskandrani/angular-dynamic-cell-mat-table
Stackblitz:
https://stackblitz.com/edit/angular-dynamic-cell-mat-table?file=app%2Ftable-basic.component.ts

Members of material repo: @andrewseguin
I was going to PR a proper solution based on this inside material repo.
A solution so to allow passing a component to a cell.
I want to know first if this is wanted? or better to wrap mat-table outside material if we want it and keep the mat-table lean.
Thank you

update: 2020-12-13
note to dynamic compile in case: https://github.com/gund/ng-dynamic-component#readme

@andrewseguin
Copy link
Contributor

Hey @nasreddineskandrani - looks like a neat way of doing dynamic components. I think if we had such a feature, it would likely go the route of using CdkPortal. That said, we don't currently have plans on bringing something like this into the repository right now

@plixxer
Copy link

plixxer commented Aug 7, 2019

For those interested, here was my approach

https://plnkr.co/edit/UntMipJO7lQVFCCSq3sZ?p=preview

<!-- Generic column definition -->
<ng-container *ngFor="let column of columns" [cdkColumnDef]="column.columnDef">
  <md-header-cell *cdkHeaderCellDef>{{ column.header }}</md-header-cell>
  <md-cell *cdkCellDef="let row">{{ column.cell(row) }}</md-cell>
</ng-container>
/** Table columns */
columns = [
  { columnDef: 'userId',    header: 'ID',       cell: (row: UserData) => `${row.id}`        },
  { columnDef: 'userName',  header: 'Name',     cell: (row: UserData) => `${row.name}`      },
  { columnDef: 'progress',  header: 'Progress', cell: (row: UserData) => `${row.progress}%` }
];

/** Column definitions in order */
displayedColumns = this.columns.map(x => x.columnDef);

EDIT: Here is a (hopefully) evergreen stackblitz with the exact same approach

https://stackblitz.com/edit/material2-beta11-vmwjpe

Thank you good Sir, I am converting a bootstrap UI to a material UI and i was stuck on this for quite some time.

@lonnenjunior
Copy link

lonnenjunior commented Oct 25, 2019

@fxck CdkTable has addColumnDef and removeColumnDef methods. It's a little clunky to use, but I think it does what you need

https://stackblitz.com/edit/angular-i4dftq?file=app/cdk-table.ts

@willshowell Thanks for this answer it helped me a lot. Currently I'm trying to create a generic data table where I pass only the matColumnDef and everything else is done automaticaly (Paginator, Selection, ...) Unfortunately, there is no possibility to add the mat-sort-header using the addColumn function. Is there any other possibility to do it?

https://stackblitz.com/edit/angular-i4dftq-urhcpg

@isaif
Copy link

isaif commented Nov 8, 2019

@paco76

Note I just copy-pasted syntax from my original comment. It's from an outdated beta version of Material and should be updated to the current syntax

<!-- Button column -->
<ng-container ... ></>

<!-- Checkbox column -->
<ng-container ... ></>

<!-- Generic column definition for dynamic columns -->
<ng-container *ngFor="let column of columns" [cdkColumnDef]="column.columnDef">
  <md-header-cell *cdkHeaderCellDef>{{ column.header }}</md-header-cell>
  <md-cell *cdkCellDef="let row">{{ column.cell(row) }}</md-cell>
</ng-container>
/** Static columns */
staticColumns = ['button', 'checkbox'];

/** Dynamically generated columns */
dynamicColumns = [
  { columnDef: 'userId',    header: 'ID',       cell: (row: UserData) => `${row.id}`        },
  { columnDef: 'userName',  header: 'Name',     cell: (row: UserData) => `${row.name}`      },
  { columnDef: 'progress',  header: 'Progress', cell: (row: UserData) => `${row.progress}%` }
];

/** Column definitions in order */
displayedColumns = [...this.staticColumns, ...this.columns.map(x => x.columnDef)];

this works but the index of static columns is returned as undefined. Do you have any idea on how to solve this?

@iamsank8
Copy link

iamsank8 commented Apr 10, 2020

I was able to use this approach to add dynamic columns to my table by specifying template from parent. However, I was not able to add pipes to template.

{ field: 'date', label: 'Date', template: (row: any) => ${row['date']} }
I want to add date pipe to this column.

This is how I am rendering cell definition.
<mat-cell *matCellDef="let row"><div [innerHTML]="column.template(row)"></div></mat-cell>

UPDATE
I was able to find this example which used implementation of pipe. It is working for me.
Link to My Savior

@tadlakha9
Copy link

Check out this link to create the Angular Datatable(P-Table) with Dynamic Columns, pagination, sorting, column level filtering, Reordering and Resizing of Column

https://www.youtube.com/watch?v=zDvzBK-xyAM

@parijat93sharma
Copy link

parijat93sharma commented Aug 27, 2020

i am still stuck at something like this,My Table has no headers and the first column of the table is like the header.
I need a mat-table which will have columns based on an array of Object and each column will have 4 normal textfields and 8 dropdowns,and these will be pre filled with some data or empty depending on the array of Object;

Something like this

MY DATASOURCE AT FIRST WILL HAVE ONLY

dataSource =[{label:'Name'}
{'label':Hobby},{'label':Address},
{'label:'state'}];

Name | Arnold | Berrry | Pickachu

hobby | Football | Baseball | Cricket

Address | dropdown | dropdown | dropdown

State | multiselect | multiselect | multiselect

CAN ANYONE PLEASE HELP ME WITH THIS APPROACH ??? @isaif @paco76 @plixxer @iamsank8

I also need to insert Columns on click of a button at a later time,Now the problem is i will have to insert a key:Value pair in each of the objects and those keys should also be dynamic otherswise it will be a duplicate

@kargozerov
Copy link

kargozerov commented Nov 5, 2020

      <ng-container *ngFor="let column of columns" [cdkColumnDef]="column.columnDef">
        <mat-header-cell *cdkHeaderCellDef>{{ column.header }}</mat-header-cell>
        <mat-cell *cdkCellDef="let row" >{{ row[column.columnDef] }}</mat-cell>
      </ng-container>

  columns = [
    { columnDef: 'position', header: 'No.'},
    { columnDef: 'name',     header: 'Name'},
    { columnDef: 'weight',   header: 'Weight'},
    { columnDef: 'symbol',   header: 'ТЕСТ'},
  ];

const row = [
  {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
  {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
  {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
  {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
];

@Khaaz
Copy link

Khaaz commented May 27, 2021

I know this was marked as closed, but I have issues with this.
While I am able to have dynamic columns like this working perfectly, mat-sort-header doesn't work with it.
Apparently, if I declare each column individually it works, if I do it with the above syntax, it created the columns dynamically but the sorting doesn't work. It actually doesn't even show the sorting arrows.

<table mat-table [dataSource]="dataSource" matSort>
			
	<ng-container *ngFor="let column of columns" [matColumnDef]="column.def" >
		<th mat-header-cell *matHeaderCellDef> {{column.header}} </th>
		<td mat-cell *matCellDef="let element"> {{column.render(element)}} </td>
	</ng-container>
	<tr mat-header-row *matHeaderRowDef="columnDefs"></tr>
	<tr mat-row *matRowDef="let row; columns: columnDefs;"></tr>
</table>
columns = [
	{ def: "id", header: "ID", render: (d: Quotation): number => d.id },
	{ def: "name", header: "Devis", render: (d: Quotation): string => d.name },
	{ def: "client", header: "Client", render: (d: Quotation): string => d.client },
	{ def: "project", header: "Projet", render: (d: Quotation): string => d.project },
];

get columnDefs(): string[] {
	return this.columns.map(e => e.def);
}

@andrewseguin
Copy link
Contributor

Looks like you are missing a mat-sort-header on your headers

@Khaaz
Copy link

Khaaz commented May 28, 2021

Oh wow, that works thank you so much. I can't believe I missed this I litereally spent 3 hours on this and I couldn't figure it out.
Thanks a lot! Have a good day!

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Jun 28, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: material/table docs This issue is related to documentation P4 A relatively minor issue that is not relevant to core functions
Projects
None yet
Development

Successfully merging a pull request may close this issue.