-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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
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 |
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. |
I think he outlined it pretty well here: //----- 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 :-) |
Hi, |
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:
EDIT: The problem was that I needed to include the following, inside of <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 |
@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, Any suggestions appreciated! |
@boombard , I've tried you template code. It looks the binding you use => [attr.mat-sort-header] is bind to the DOM, not a property binding. |
@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)
|
@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? |
And here is a more complicated one with sorting, checkboxes and filtering FYI
|
Hi jimmykane, thank you! |
@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> {{ 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! :) |
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 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 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/ |
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]? |
Yes, I have created a library that does that: https://www.npmjs.com/package/material-dynamic-table |
Try doing <md-cell *cdkCellDef="let row"> {{row[progress]}} Good Luck!! |
Hi! So this is a working draft that shows how to pass a Example: Members of material repo: @andrewseguin update: 2020-12-13 |
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 |
Thank you good Sir, I am converting a bootstrap UI to a material UI and i was stuck on this for quite some time. |
@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? |
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.
This is how I am rendering cell definition. UPDATE |
Check out this link to create the Angular Datatable(P-Table) with Dynamic Columns, pagination, sorting, column level filtering, Reordering and Resizing of Column |
i am still stuck at something like this,My Table has no headers and the first column of the table is like the header. Something like this MY DATASOURCE AT FIRST WILL HAVE ONLY dataSource =[{label:'Name'} Name | Arnold | Berrry | Pickachuhobby | Football | Baseball | CricketAddress | dropdown | dropdown | dropdownState | multiselect | multiselect | multiselectCAN 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 |
|
I know this was marked as closed, but I have issues with this. <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);
} |
Looks like you are missing a |
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. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
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:
@andrewseguin
The text was updated successfully, but these errors were encountered: