Angular Material Table Dynamic Columns without model
DynamicInterfaceAngular MaterialDatasourceDynamic Problem Overview
I need to use angular material table without model, because I don't know what will come from service.
So I am initializing my MatTableDataSource
and displayedColumns
dynamically in component like that :
TableComponent :
ngOnInit() {
this.vzfPuanTablo = [] //TABLE DATASOURCE
//GET SOMETHING FROM SERVICE
this.listecidenKisi = this.listeciServis.listecidenKisi;
this.listecidenVazife = this.listeciServis.listecidenVazife;
//FILL TABLE DATASOURCE
var obj = {};
for (let i in this.listecidenKisi ){
for( let v of this.listecidenVazife[i].vazifeSonuclar){
obj[v.name] = v.value;
}
this.vzfPuanTablo.push(obj);
obj={};
}
//CREATE DISPLAYED COLUMNS DYNAMICALLY
this.displayedColumns = [];
for( let v in this.vzfPuanTablo[0]){
this.displayedColumns.push(v);
}
//INITIALIZE MatTableDataSource
this.dataSource = new MatTableDataSource(this.vzfPuanTablo);
}
The most important part of code is here :
> lang-ts >for( let v in this.vzfPuanTablo[0]) { > this.displayedColumns.push(v); >} >
I am creating displayedColumns
here dynamically, it means; even I don't know what will come from service, I can show it in table.
For example displayedColumns
can be like that:
- ["one", "two" , "three" , "four" , "five" ]
or
- ["stack","overflow","help","me]
But it is not problem because I can handle it.
But when I want to show it in HTML, I can't show properly because of
matCellDef
thing:
TableHtml :
<mat-table #table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let disCol of displayedColumns; let colIndex = index" matColumnDef="{{disCol}}">
<mat-header-cell *matHeaderCellDef>{{disCol}}</mat-header-cell>
<mat-cell *matCellDef="let element "> {{element.disCol}}
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
My problem is here:
>lang-xml > <mat-cell *matCellDef="let element "> {{element.disCol}} < / mat-cell> >
In fact, I want to display element."disCol's value"
in the cell, but I don't know how can I do that.
Otherwise, everything is ok except this element."disCol's value"
thing.
When I use {{element.disCol}}
to display value of element that has disCols's value
, all cells are empty like that:
Other example that using {{element}}
only:
Also as you can see:
-
Table datasource is changing dynamically. It means I can't use
{{element.ColumnName}}
easily, because I don't know even what is it.- First Example's displayedColumns = ['Vazife', 'AdSoyad', 'Kirmizi', 'Mavi', 'Yesil', 'Sari'];
- Second Example's displayedColumns = ['Muhasebe', 'Ders', 'Egitim', 'Harici'];
-
matHeaderCellDef
is correct , because it is using {{disCol}} directly.
But I need to read disCol's value, and display element.(disCol's value)
in the cell.
How can I do that ?
Dynamic Solutions
Solution 1 - Dynamic
I found solution :) It is very very easy but i could't see at first :) only like that :
>
I must use {{element[disCol]}}
only in HTML.
Now , everything is ok:)
Solution 2 - Dynamic
For a full working example based on @mevaka's
Where jobDetails$
is the array of items.
columns$
is equvilent to Object.keys(jobDetails$[0])
so is just an string[]
<table mat-table [dataSource]="jobDetails$ | async">
<ng-container *ngFor="let disCol of (columns$ | async); let colIndex = index" matColumnDef="{{disCol}}">
<th mat-header-cell *matHeaderCellDef>{{disCol}}</th>
<td mat-cell *matCellDef="let element">{{element[disCol]}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="(columns$ | async)"></tr>
<tr mat-row *matRowDef="let row; columns: (columns$ | async)"></tr>
</table>
Solution 3 - Dynamic
I've tried my best to boil a dynamic table down to the minimum. This example will display any columns given an array of flat objects with any keys. Note how the first object has an extra "foo" property that causes an entire column to be created. The DATA const could be some data you get from a service. Also, you could add a "column ID -> label" mapping into this if you know some common property names you'll be getting the JSON. See the stachblitz here.
import {Component, ViewChild, OnInit} from '@angular/core';
const DATA: any[] = [
{position: 1, name: 'sdd', weight: 1.0079, symbol: 'H', foo: 'bar'},
{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'},
{position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
{position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'}
];
@Component({
selector: 'dynamic-table-example',
styleUrls: ['dynamic-table-example.css'],
templateUrl: 'dynamic-table-example.html',
})
export class DynamicTableExample implements OnInit {
columns:Array<any>
displayedColumns:Array<any>
dataSource:any
ngOnInit() {
// Get list of columns by gathering unique keys of objects found in DATA.
const columns = DATA
.reduce((columns, row) => {
return [...columns, ...Object.keys(row)]
}, [])
.reduce((columns, column) => {
return columns.includes(column)
? columns
: [...columns, column]
}, [])
// Describe the columns for <mat-table>.
this.columns = columns.map(column => {
return {
columnDef: column,
header: column,
cell: (element: any) => `${element[column] ? element[column] : ``}`
}
})
this.displayedColumns = this.columns.map(c => c.columnDef);
// Set the dataSource for <mat-table>.
this.dataSource = DATA
}
}
<mat-table #table [dataSource]="dataSource">
<ng-container *ngFor="let column of columns" [cdkColumnDef]="column.columnDef">
<mat-header-cell *cdkHeaderCellDef>{{ column.header }}</mat-header-cell>
<mat-cell *cdkCellDef="let row">{{ column.cell(row) }}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>