Components: [Table] Add example with dynamic columns

Created on 21 Jul 2017  路  55Comments  路  Source: angular/components

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

P4 materiatable docs

Most helpful comment

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

All 55 comments

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

Is it possible to internationalize these dynamic columns implementation?

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.

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.

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

@eltimmo updated the original comment!

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?

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.

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

PEBCAK

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

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

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>

@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

@andrewseguin thanks! That was exactly it. 馃檪

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!

@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

@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>

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

Jeee! This is not added to the docs yet?

any update on that guys , the above code not working with the latest version

@HDaghash Works still for me.

@jimmykane i handled that in different way , it seems ur not using the latest version of angular material

this guy "cdkHeaderCellDef" not there anymore .

anyway it's sorted

Thanks :)

I was refering to @donmccurdy s answer.

the cdkHeaderCellDef of course it's there

https://material.angular.io/components/table/api#MatHeaderCellDef

and if I want a checkbox or a button to be added for each row? how is it possible using @willshowell approach?

@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)];

Hi @willshowell, I tried your approach and it works great but I was wondering how to add a checkbox or a button for each row of the table? thank you!

and how to add buttons?

@willshowell had some pseudo-code showing that you would just define the checkbox or button template as a column definition:

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

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

Which, with a checkbox would look something like:

<ng-container matColumnDef="myColumn">
  <md-header-cell *cdkHeaderCellDef>{{ column.header }}</md-header-cell>
  <md-cell *cdkCellDef="let row">
    <mat-checkbox [(ngModel)]="row.checked"></mat-checkbox>
  </md-cell>
</ng-container>

People may be able to provide better feedback if you explain what you have tried and what is not working for you.

I want to go through the object discarding the column actions with an *ngIF but it does not work, it shows me the following error.

Error: Duplicate column definition name provided: "actions".

columns = ['id', 'name', 'actions'];

    <ng-container matColumnDef="{{column}}" *ngFor="let column of displayedColumns">
        <div *ngIf="column!='actions'">
            <mat-header-cell *matHeaderCellDef> {{column}} </mat-header-cell>
            <mat-cell *matCellDef="let element">
            {{element[column]}}
            </mat-cell>
        </div>
    </ng-container>
    <ng-container cdkColumnDef="actions">
            <mat-header-cell > Actions </mat-header-cell>
            <mat-cell *cdkCellDef="let element" >
                 <button md-raised-button >Edit</button> 
            </mat-cell>
    </ng-container> 

This should work:
<div *ngFor="let displayedColumn of displayedColumns; let columnIndex = index"> <ng-container *ngIf="displayedColumn !='actions'" matColumnDef="{{displayedColumn}}"> <mat-header-cell *matHeaderCellDef mat-sort-header>{{displayedColumn}}</mat-header-cell> <mat-cell *matCellDef="let element "> {{element[displayedColumn]}}</mat-cell> </ng-container> </div> <ng-container matColumnDef="actions"> <mat-header-cell *matHeaderCellDef >Actions</mat-header-cell> <mat-cell *matCellDef="let element "> <button mat-raised-button> Edit </button> </mat-cell> </ng-container>

that worked, thanks @DaDave

Hi @DaDave,
can you please connect all parts together and show one working example?

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>

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

@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.

Hi @natecowen,

Any chance you can share a stackblitz?

Thanks! :)
Dave

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

@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

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

@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 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..

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

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]?

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.

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 {{row[progress]}}
This is not a bug. This was related to concatenating two literals in Interpolation.

Good Luck!!

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

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

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.

@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

@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?

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

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

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

      <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'},
];
Was this page helpful?
0 / 5 - 0 ratings

Related issues

RoxKilly picture RoxKilly  路  3Comments

Hiblton picture Hiblton  路  3Comments

LoganDupont picture LoganDupont  路  3Comments

vitaly-t picture vitaly-t  路  3Comments

shlomiassaf picture shlomiassaf  路  3Comments