Mat-table, mat-sort is not working properly - html

I tried to implement mat-sort in mat-table, in my project. After starting my project, I see "sort" icon, can click on it, but nothing sorting in my result, always that same view.
component-file:
#Component({
selector: 'app-test-component',
templateUrl: './testComponent.component.html',
styleUrls: ['./testComponent.component.css'],
providers: [TestService],
})
export class TestComponent implements OnInit, OnDestroy, AfterViewInit {
displayedColumns = ['Column1'];
dataSource = new MatTableDataSource<UserModel>();
private exDisplayNames: Subscription;
#ViewChild(MatSort) sort: MatSort;
constructor(private testService: TestService) { }
ngOnInit() {
this.exDisplayNames = this.testService.userNamesChanged.subscribe((userNameData: UserModel[]) => {
this.dataSource.data = userNameData;
});
this.testService.fetchUserName();
}
ngAfterViewInit() {
this.dataSource.sort = this.sort;
}
ngOnDestroy() {
if (this.exDisplayNames) {
this.exDisplayNames.unsubscribe();
}
}
}
service file:
#Injectable()
export class TestService {
userNamesChanged = new Subject<UserModel[]>();
private availableUserName: UserModel[] = [];
private fbSubs: Subscription[] = [];
constructor(private db: AngularFirestore) { }
fetchUserName() {
this.fbSubs.push(this.db.collection('names/')
.snapshotChanges()
.map(docArray => {
return docArray.map(doc => {
return {
displayUserName: doc.payload.doc.data().nick,
};
});
})
.subscribe((userData: UserModel[]) => {
this.availableUserName = userData;
this.userNamesChanged.next([...this.availableUserName]);
}, error => {
console.log(error);
}));
}
}
html file:
<section fxLayoutAlign="center">
<mat-card>
<mat-card-content>
<mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="Column1">
<mat-header-cell *matHeaderCellDef mat-sort-header>Nick</mat-header-cell>
<mat-cell fxLayout="row" fxLayoutAlign="center center" *matCellDef="let element">{{ element.displayUserName}}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</mat-card-content>
</mat-card>
</section>
Also was imported all necessary material modules: MatSortModule, MatTabsModule.
My steps are:
1. Added in component: #ViewChild(MatSort) sort: MatSort.
2. Implements AfterViewInit.
3. Added:
ngAfterViewInit() {
this.dataSource.sort = this.sort;
}
In html file added: matSort in <mat-table>.
Added mat-sort-header to <mat-cell-header>.
Like I wrote: I see sort icon on the column, but after clicking - nothing changing, nothing sorting.

I'm not sure how your UserModel looks, but I don't think it contains a property Column1:
matColumnDef="Column1"
Change that part to something like matColumnDef="displayUserName" or whatever property you have in the model.
Also change displayedColumns = ['displayUserName'];
From the docs:
By default, the MatTableDataSource sorts with the assumption that the sorted column's name matches the data property name that the column displays. For example, the following column definition is named position, which matches the name of the property displayed in the row cell.

ngOnInit() {
this.exDisplayNames = this.testService.userNamesChanged.subscribe((userNameData: UserModel[]) => {
this.dataSource = new MatTableDataSource(userNameData);
setTimeout(() => this.dataSource.sort = this.sort);
});
this.testService.fetchUserName();
}
Your sorter is bound to your datasource at view init, but it doesn't wait for your request to be made.
You should create a new instance of a data source and stop fiddling in the properties. Material provides an high level of abstraction, take advantage of that.
N.B. : In my solution you can see a timeout, it's for change detection. It is possible that you don't need it, I just put it there just to be sure.

Related

Angular Material Table is not showing data. How do I achieve this? I get 'undefined' w/ console.log(data.items). I receive data w/ console.log(data)

I am receiving an object when I enter console.log(data) but undefined
when I enter console.log(items). I am trying to enter the data in the
interface then display the entries in the table.
component.ts
import {HttpClient} from '#angular/common/http';
import {Component, ViewChild, AfterViewInit} from '#angular/core';
import {MatPaginator} from '#angular/material/paginator';
import {MatSort, SortDirection} from '#angular/material/sort';
import {merge, Observable, of as observableOf} from 'rxjs';
import {catchError, map, startWith, switchMap} from 'rxjs/operators';
import { MatTable } from '#angular/material/table';
import { AssetTableDataSource, AssetTableItem } from './asset-table-datasource';
import { AssetTableModule } from './asset-table.module';
#Component({
selector: 'app-asset-table',
templateUrl: './asset-table.component.html',
styleUrls: ['./asset-table.component.scss']
})
export class AssetTableComponent implements AfterViewInit {
displayedColumns: string[] = ['client_id','periodenddate', 'dataitemname', 'dataitemvalue'];
assetDatabase!: AssetRecHttpDatabase | null;
data: ClientidAssetData[] = [];
items: any[] =[];
resultsLength = 0;
isLoadingResults = true;
isRateLimitReached = false;
#ViewChild(MatPaginator)
paginator!: MatPaginator;
#ViewChild(MatSort)
sort!: MatSort;
constructor(private _httpClient: HttpClient) {}
ngAfterViewInit() {
this.assetDatabase = new AssetRecHttpDatabase(this._httpClient);
// If the user changes the sort order, reset back to the first page.
this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));
merge(this.sort.sortChange, this.paginator.page)
.pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults = true;
return this.assetDatabase!.getAssetresults(
this.sort.active,
this.sort.direction,
this.paginator.pageIndex,
).pipe(catchError(() => observableOf(null)));
}),
map(data => {
// Flip flag to show that loading has finished.
this.isLoadingResults = false;
this.isRateLimitReached = data === null;
if (data === null) {
return [];
}
// Only refresh the result length if there is new data. In case of rate
// limit errors, we do not want to reset the paginator to zero, as that
// would prevent users from re-triggering requests.
this.resultsLength = data.total_count;
console.log(data);
console.log(data.items);
return data;
}),
)
.subscribe(data => (data = data));
}
}
export interface GetAssetApi {
items: ClientidAssetData[];
total_count: number;
}
export interface ClientidAssetData{
Clientid: string;
PeriodEndDate: string;
DataItemName: string;
DataItemValue: string;
}
/** The database that the data source uses to retrieve data for the table. */
export class AssetRecHttpDatabase {
constructor(private _httpClient: HttpClient) {}
getAssetresults(sort: string, order: SortDirection, page: number): Observable<GetAssetApi> {
const href = 'http://localhost:3000/institutionasset/assetresults/';
const requestUrl = `${href}?fldinstid=3&flddataitem=Data Item:angular/material&sort=${sort}&order=${order}&page=${
page + 1
}`;
return this._httpClient.get<GetAssetApi>(requestUrl);
}
}
html
<div class="example-container mat-elevation-z8">
<div class="example-loading-shade"
*ngIf="isLoadingResults || isRateLimitReached">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
<div class="example-rate-limit-reached" *ngIf="isRateLimitReached">
The rate limit has been reached. It will be reset in one minute.
</div>
</div>
<div class="example-table-container">
<table mat-table [dataSource]="data" class="example-table"
matSort matSortActive="client_id" matSortDisableClear matSortDirection="desc">
<!-- Period End Date Column -->
<ng-container matColumnDef="periodenddate">
<th mat-header-cell *matHeaderCellDef>Period End Date</th>
<td mat-cell *matCellDef="let row">{{row.PeriodEndDate | date}}</td>
</ng-container>
<!-- Data Item Value Column -->
<ng-container matColumnDef="dataitemvalue">
<th mat-header-cell *matHeaderCellDef>Data Item Value</th>
<td mat-cell *matCellDef="let row">{{row.DataItemValue}}</td>
</ng-container>
<!-- Data Item Column -->
<ng-container matColumnDef="dataitemname">
<th mat-header-cell *matHeaderCellDef>Data Item Name</th>
<td mat-cell *matCellDef="let row">{{row.DataItemName}}</td>
</ng-container>
<!-- Client id Column -->
<ng-container matColumnDef="client_id">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>
Client id
</th>
<td mat-cell *matCellDef="let row">{{row.Clientid}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<mat-paginator [length]="resultsLength" [pageSize]="30" aria-label="Select page of Asset"></mat-paginator>
</div>
I get my data from the api and this works.. I get four items in the
array and in console it says object and within the dropdown of objects
in the console it says the field and it's values.
export class InstitutionassetService {
constructor(#InjectDataSource() private dataSource: DataSource) {}
async find(fldinstid: string, flddataitem: string) {
const instassets = await this.dataSource.query(
"dbo.WebGetClientCallSheet #Clientid='" +
fldinstid +
"'," +
"#DataItemName='" +
flddataitem +
"'",
);
return instassets;
}
}
import { Controller, Get, Query } from '#nestjs/common';
import { InstitutionassetService } from './institutionasset.service';
controller from nestjs
#Controller('institutionasset')
export class InstitutionassetController {
constructor(private institutionassetService: InstitutionassetService) {}
#Get('/assetresults')
async find(
#Query('fldinstid') fldinstid: string,
#Query('flddataitem') flddataitem: string,
) {
console.log(fldinstid + ' ' + flddataitem);
return await this.institutionassetService.find(fldinstid, flddataitem);
}
}

Pagination, Sort, Filter fails in Angular Material Table for big JSON file

I am trying to send JSON with more than 50K entries via an express server to Angular. In the deployment I can see all the entries in the format of Angular Material table. However the pagination, sort and filter feature of the tables are not working at all.
Here is the ts file
import { MatPaginator } from '#angular/material/paginator';
import { MatSort } from '#angular/material/sort';
import { Component, ViewChild } from '#angular/core';
import { MatTable, MatTableDataSource } from '#angular/material/table';
import { ViewDataService } from './modules/view-data.service';
import { ThrowStmt } from '#angular/compiler';
import { DataSource } from '#angular/cdk/collections';
import { BehaviorSubject } from 'rxjs';
export interface DataEntries {
key: string;
value: String;
}
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'TD';
dataSource: any; //MatTableDataSource<Student>;
displayedColumns: string[] = ['key', 'value'];
//columns: string[] = ['id','name','email','gender']
public orderByKey(a: any, b: any) {
return a.key;
}
private paginator: MatPaginator;
private sort: MatSort;
#ViewChild(MatSort) set matSort(ms: MatSort) {
this.sort = ms;
this.setDataSourceAttributes();
}
#ViewChild(MatPaginator) set matPaginator(mp: MatPaginator) {
this.paginator = mp;
this.setDataSourceAttributes();
}
setDataSourceAttributes() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
//#ViewChild(MatSort, { static: false }) sort: MatSort;
//#ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
dataEntries: DataEntries[] = []
constructor(private viewdata: ViewDataService) {
}
ngOnInit(): void {
this.viewdata.getData().then(response => {
this.dataEntries = response.data;
console.log(this.dataEntries);
this.dataSource = new MatTableDataSource(this.dataEntries);
console.log(this.dataSource);
this.dataSource = this.dataSource.filteredData;
this.dataSource.sort = this.sort;
setTimeout(() => this.dataSource.paginator = this.paginator);
})
//Called after the constructor, initializing input properties, and the first call to ngOnChanges.
//Add 'implements OnInit' to the class.
}
applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.dataSource.filter = filterValue.trim().toLowerCase();
}
}
Here is the template
<h1>Placeholder Table</h1>
<mat-form-field>
<mat-label>Filter</mat-label>
<input matInput formControlName="formControlName"
type="text"
placeholder="Search" (keyup)="applyFilter($event)">
</mat-form-field>
<mat-table [dataSource]="dataSource | keyvalue:orderByKey" class="mat-elevation-z4" matSort>
<ng-container matColumnDef="key">
<mat-header-cell *matHeaderCellDef mat-sort-header> Key
</mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.key}} </mat-cell>
</ng-container>
<ng-container matColumnDef="value">
<mat-header-cell *matHeaderCellDef mat-sort-header> value</mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.value}} </mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
<mat-paginator [length]="dataEntries.length"
[pageSize]="100"
[pageSizeOptions]="[2, 5, 25, 100]">
</mat-paginator>
<router-outlet></router-outlet>
Here is the express file:
var express = require('express');
var router = express.Router();
const jason = require('../public/jsons/en-GB.json');
router.get('/', function(req, res, next) {
res.json(JSON.parse(JSON.stringify(jason)));
console.log('Test');
});
module.exports = router;
Please let me know why they are failing
In your app.component.ts
async ngOnInit(): void {
const response = await this.viewdata.getData();
this.dataEntries = response.data;
console.log(this.dataEntries);
this.dataSource = new MatTableDataSource(this.dataEntries);
console.log(this.dataSource);
this.dataSource = await this.dataSource.filteredData;
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator
}
}
In your http-service
getData(){
return http.get('/').toPromise();
}

Merge two Observables and pass into dataSource as a single Observable for Angular material table

I am fairly new to Web development and am stuck on this problem from past some days, Would appreciate a heck lot if the community could help me out here
I want to merge two observables coming out of firebase database, I want to join them concurrently i.e
3 rows of the first observable to be mapped with the three rows from the other observable. I want to use this observable into my dataSource for Angular Mat Table. and extract fields from both the observable here
This is the Component.ts
import { Component, OnInit } from '#angular/core';
import { ProductService } from 'src/app/product.service';
import { Observable, observable } from 'rxjs';
import "rxjs/add/observable/zip";
import "rxjs/add/observable/forkJoin";
#Component({
selector: 'app-admin-products',
templateUrl: './admin-products.component.html',
styleUrls: ['./admin-products.component.css']
})
export class AdminProductsComponent implements OnInit {
products$:Observable<any[]>
productsKey$:Observable<any[]>
finalProduct$
list:[]
constructor(private productService:ProductService) {
this.products$ = this.productService.getAll().valueChanges();
this.productsKey$ = this.productService.getAll().snapshotChanges();
//this.finalProduct$ = (this.products$).pipe(merge(this.productsKey$));
}
displayedColumns = ['title','price','edit'];
// dataSource = this.products$
ngOnInit(): void {
}
}
This is the service.ts
import { Injectable } from '#angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
#Injectable({
providedIn: 'root'
})
export class ProductService {
constructor(private db:AngularFireDatabase) { }
create(product){
console.log(product)
return this.db.list('/products').push(product);
}
getAll(){
return this.db.list('/products')
}
}
this is the final HTML markup
<p style="padding: 50px;">
<button mat-flat-button color="primary" routerLink="/admin/products/new" > Add New Product</button>
</p>
<mat-table [dataSource]="finalProduct$ | async" class="mat-elevation-z8">
<!-- Position Column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef> Title </mat-header-cell>
<mat-cell *matCellDef="let element"> {{ element.title }} </mat-cell>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="price">
<mat-header-cell *matHeaderCellDef> Price </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.price}}</mat-cell>
</ng-container>
<ng-container matColumnDef="edit">
<mat-header-cell *matHeaderCellDef> </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element | json}} </mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
The first Observable products$ has data in this format
{category: "bread", imageUrl: "https://pixabay.com/photos/bread-food-isolated-croissant-loaf-4592483/", price: 50, title: "Freshly Baked Bread"}
the Second observable productsKey$ has data in this format
{payload: DataSnapshot, type: "value", prevKey: null, key: "-M9HwZl_WYfgTchxanrb"}
I wish to extract the Price, title and key value from these observables and display them in a table.
I would suggest using zip which does exactly what you want, i.e merge 2 observables.
ngOnInit(): void {
zip(this.products$, this.productsKey$).pipe(
map(reponse => { return {...reponse[0], ...response[1]}})
).subscribe(
reponse => {
this.dataSource = reponse;
});
You can also use combineLatest as Michael suggested with slight modifications. Change map(reponse => [...reponse[0], ...response[1]]) to map(reponse => { return {...reponse[0], ...response[1]}})
If the observables are independent of each other you could use RxJS combineLatest() function to combine multiple observables.
displayedColumns = ['title', 'price', 'edit'];
constructor(private productService: ProductService) {
this.products$ = this.productService.getAll().valueChanges();
this.productsKey$ = this.productService.getAll().snapshotChanges();
}
ngOnInit(): void {
combineLatest(this.products$, this.productsKey$).pipe(
take(1), // <-- remove it if the data stream needs to persist
map(reponse => [...reponse[0], ...response[1]])
).subscribe(
reponse => {
this.dataSource = reponse;
}
);
}

Angular dynamically populate table

I'm trying to dynamically populate a table using the following code:
teams.component.ts
import { Component, OnInit } from '#angular/core';
import { first } from 'rxjs/operators';
import { TeamService } from 'src/app/services/team.service';
export interface PeriodicElement {
name: string;
position: number;
weight: number;
symbol: string;
}
#Component({
selector: 'app-teams',
templateUrl: './teams.component.html',
styleUrls: ['./teams.component.scss']
})
export class TeamsComponent implements OnInit {
public teams : any;
tableCols = ['id', 'name'];
constructor(private teamService : TeamService) { }
ngOnInit(): void {
this.teamService.getTeams().pipe(first()).subscribe(
data => {
this.teams = data.results
},
error => {
console.log(error.error)
}
)
}
}
teams.component.html
<app-table [tableData]="teams" [tableColumns]="tableCols"></app-table>
table.component.ts
import { Component, OnInit, Input, ViewChild } from '#angular/core';
import { MatSort } from '#angular/material/sort';
import { MatPaginator } from '#angular/material/paginator';
import { MatTableDataSource } from '#angular/material/table';
#Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss']
})
export class TableComponent implements OnInit {
tableDataSrc: any;
// tslint:disable-next-line: no-input-rename
#Input('tableColumns') tableCols: string[];
#Input() tableData: {}[] = [];
#ViewChild(MatSort, { static: true }) sort: MatSort;
#ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
constructor() { }
ngOnInit() {
this.tableDataSrc = new MatTableDataSource(this.tableData);
this.tableDataSrc.sort = this.sort;
this.tableDataSrc.paginator = this.paginator;
}
}
table.component.html
<div class="mat-elevation-z8">
<table mat-table [dataSource]="tableDataSrc" matSort class="mat-elevation-z8">
<ng-container *ngFor="let col of tableCols">
<ng-container matColumnDef="{{ col }}">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ col | titlecase }}
</th>
<td mat-cell *matCellDef="let profile">{{ profile }}</td>
</ng-container>
</ng-container>
<tr mat-header-row *matHeaderRowDef="tableCols"></tr>
<tr mat-row *matRowDef="let row; columns: tableCols"></tr>
</table>
<mat-paginator [pageSizeOptions]="[1, 2, 3, 5, 10, 20]" showFirstLastButtons></mat-paginator>
</div>
A 'team' object looks like the following:
{'id': 9, 'name': 'FC Barcelona'} and the teams variable is a list of these objects.
When I navigate to the teams page the table is rendered and stays empty, what am I doing wrong here?
try replacing this part in table.component.ts
ngOnInit() {
this.tableDataSrc = new MatTableDataSource(this.tableData);
this.tableDataSrc.sort = this.sort;
this.tableDataSrc.paginator = this.paginator;
}
with this
ngOnChanges(changes: SimpleChanges) {
if(changes.tableData.currentValue) {
this.tableDataSrc = new MatTableDataSource(this.tableData);
this.tableDataSrc.sort = this.sort;
this.tableDataSrc.paginator = this.paginator;
}
}
The problem is teams field in TeamsComponent gets initialized after (due to async operation) TableComponents OnInit phase. If you change ngOnInit with ngOnChanges whenever teams field changes TableComponent becomes aware of it.
Further reading: https://angular.io/guide/lifecycle-hooks#using-change-detection-hooks

Angular material table add dynamic column headers

I'm currently using a package inside an Angular project that houses reusable components I've developed for the project. How can I make the column headers/rows on a material table dynamic so that I can simply pass them as an input array when I consume the table inside my Angular project?
Some of these headers are stacked or have unique HTML/CSS, which is where I'm running into the issue. I've tried creating an array inside my component that has a boolean flag stating whether or not a column header/row should house two fields stacked on top of each other.
Below is a snippet from my current HTML that isn't dynamic. You can see that both ng-containers are different. How can i write this in such a way that when i consume my table inside my project, i can simply pass an array of columns/rows as an input?
<ng-container matColumnDef="from">
<th mat-header-cell *matHeaderCellDef>
<div [ngStyle] = "{'color': pickupHeader}" class = "stackedColumn">
<span (click)="toggleDates($event)">{{ 'shipperFrom' }}</span>
</div>
<div [ngStyle] = "{'color': deliveryHeader}" class = "stackedColumn">
<span (click) = "toggleDates($event)">{{ 'shipperTo' }}</span>
</div>
</th>
<td mat-cell *matCellDef="let element">
<div>
<span class = "location"> <img src="{{ element.Flag }}">{{element.PickupCity}}</span>
</div>
<div>
<span class = "location"><img src="{{ element.Flag }}">{{element.DeliveryCity}}</span>
</div>
</td>
</ng-container>
<ng-container matColumnDef="legs">
<th mat-header-cell *matHeaderCellDef> {{ somethingElse }} </th>
<td mat-cell *matCellDef="let element"> {{element.SomethingElse}} </td>
</ng-container>
Basically I want to do something in my component.ts that looks like this:
data = [{},{},{},{}]
and I want that array of objects to populate the table and know what kind of HTML it should use so that when I import it and consume it in my project this is all I need:
<the-table [dataSource] = "data"></the-table>
Essentially I want to be able to add columns/rows to the table on the fly without having to go back and edit the package.
I've done something similar. Keep in mind I messed with the below a little bit to strip out some project specific details, so let me know if there are issues.
component.html:
<mat-table matSort [dataSource]="dataSource">
<ng-container *ngFor="let column of displayedColumns; index as i" [matColumnDef]="column">
<mat-header-cell *matHeaderCellDef class="col-search" fxLayoutAlign="start start" fxLayout="column">
<span mat-sort-header>{{ getColumnLabel(column) }}</span>
<!--The below is incomplete Just remove if you don't need filtering-->
<input
matInput
autocomplete="off"
id="{{ column + i }}"
(keyup)="myCustomFilterCode()"
placeholder="Filter"
/>
</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row[column] }}</mat-cell>
</ng-container>
<!--This ngStyle is specifically for fixedWidth tables which are meant to be horizontally scrollable-->
<mat-header-row [ngStyle]="{ 'min-width.px': width ? width : null }" *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row [ngStyle]="{ 'min-width.px': width ? width : null }" *matRowDef="let row; columns: displayedColumns"> </mat-row>
</mat-table>
<mat-paginator [pageSizeOptions]="pageSizeOptions" showFirstLastButtons [pageSize]="startingPageSize"></mat-paginator>
component.ts
/**This component will dynamically create a mat-table for a set of data */
#Component({
selector: 'dynamic-mat-table',
templateUrl: './dynamic-mat-table.component.html',
styleUrls: ['./dynamic-mat-table.component.scss'],
providers: [DatePipe]
})
export class DynamicMatTableComponent implements OnInit, OnChanges {
/**The data to generate a table for */
#Input()
tableData: Object[];
/** This will override the column names in the table, the id will be the property name (i.e. propertyID)
* and the value will be the column header text
*
* **This is Only Necessary if you don't like the Automatically created column names**
*/
#Input()
columnLabels: IdValuePair[];
/**List of column property names (i.e. propertyID) to ignore */
#Input()
ignoreColumns: string[];
/**Sets the starting page size for the paginator */
#Input()
startingPageSize: number;
/**Sets the page size options for the paginator */
#Input()
pageSizeOptions: number[];
/**Defaults to false, when true the table will be generated with a width of # of columns * columnWidth,
* otherwise column widths will not be specified (will fill container with equal width columns) */
#Input()
fixedWidth = false;
/**Defaults to 250, Only used when fixedWidth = true if not set this determines the width of each column */
#Input()
columnWidth = 250;
width: number;
dataSource = new MatTableDataSource<Object>();
fullColumnLabels: IdValuePair[] = [];
displayedColumns: string[];
#ViewChild(MatPaginator, { static: true })
paginator: MatPaginator;
#ViewChild(MatSort, { static: true })
sort: MatSort;
constructor(private datePipe: DatePipe) {}
ngOnInit() {}
/**Generate dynamic table whenever inputs change */
ngOnChanges() {
this.initTable();
if (this.tableData && this.tableData.length > 0) {
this.displayedColumns = Object.keys(this.tableData[0]);
this.removeIgnoredColumns();
this.createLabelsForColumns(this.displayedColumns);
this.calculateRowWidth(this.displayedColumns.length);
this.dataSource.data = this.pipeData([...this.tableData]);
}
}
/**Create the labels array for each column */
private createLabelsForColumns(columns: string[]) {
this.fullColumnLabels = this.columnLabels;
if (this.fullColumnLabels === undefined) {
this.fullColumnLabels = [];
}
if (this.tableData && this.tableData.length > 0) {
columns.forEach(x => {
if (!this.fullColumnLabels.some(label => label.id === x)) {
this.fullColumnLabels.push(new IdValuePair(x, _.startCase(x)));
}
});
}
}
/**Remove ignored columns to prevent from being displayed */
private removeIgnoredColumns() {
if (this.ignoreColumns) {
this.displayedColumns = this.displayedColumns.filter(x => !this.ignoreColumns.some(y => y === x));
}
}
/**Calculate the row width by the number of columns */
private calculateRowWidth(columnNumber: number) {
if (this.fixedWidth) {
this.width = columnNumber * this.columnWidth;
}
}
/**Initialize table */
private initTable() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
/**Cleans up data with pipes if necessary*/
private pipeData(data: Object[]): Object[] {
data = this.pipeDates(data);
return data;
}
/**Pipe dates through a date pipe if the property name contains 'date' and the value looks like a date*/
private pipeDates(data: Object[]): Object[] {
// ISO_8601 is what .net core returns, may need to expand this list
const formats = [moment.ISO_8601];
// Loop through each row of data
data.forEach((row, index) => {
// Loop through each property in each row
Object.keys(data[index]).forEach(propertyName => {
// Get the value of the property
const value = data[index][propertyName];
// If the value matches a format in the format list, then transform it to a short date
if (propertyName.toLowerCase().search('date') !== -1 && moment(value, formats, true).isValid()) {
data[index][propertyName] = this.datePipe.transform(value, 'short');
}
});
});
return data;
}
/**Gets the label for a given column from the property name */
getColumnLabel(column: string): string {
return this.fullColumnLabels.find(x => x.id === column).value;
}
}