We are currently developing an application based on Angular2 that is quite data-heavy. In order to show this data, we decided to give ngx-datatables
We decided to go with the solution having the column definitions in the .ts file.
Here is our solution:
grid.component.html
<div class="ngx-datatable material">
<div class="datatable-footer datatable-footer-inner">
<div class="page-count">
Show
<select (change)="onLimitChange($event.target.value)" class="page-limit">
<option
*ngFor="let option of pageLimitOptions"
[value]="option.value"
[selected]="option.value == currentPageLimit">
{{option.value}}
</option>
</select>
per page
</div>
</div>
<ngx-datatable
class="material striped"
[columns]="columns"
[columnMode]="'force'"
[rows]="gridModel.Data"
[headerHeight]="'auto'"
[footerHeight]="'auto'"
[rowHeight]="'auto'"
[externalPaging]="true"
[externalSorting]="true"
[count]="gridModel?.TotalElements"
[offset]="gridModel?.CurrentPageNumber"
[limit]="gridModel?.PageSize"
[loadingIndicator]="isLoading"
(page)='loadPage($event)'
(sort)="onSort($event)">
</ngx-datatable>
</div>
<app-spinner [isRunning]="isLoading"></app-spinner>
<ng-template #emptyTemplate let-row="row" let-value="value"></ng-template>
<ng-template #idAnchorEditTemplate let-row="row" let-value="value">
<a [routerLink]="['edit', value]" class="btn btn-sm btn-outline-primary">
<i class="fa fa-pencil" aria-hidden="true"></i>
</a>
</ng-template>
<ng-template #dateTemplate let-row="row" let-value="value">
{{value | date:'dd.MM.yyyy' }}
</ng-template>
<ng-template #dateTimeTemplate let-row="row" let-value="value">
{{value | date:'dd.MM.yyyy HH:mm:ss' }}
</ng-template>
grid.component.ts
import { Component, Injectable, Input, Output, OnInit, OnDestroy, EventEmitter, ViewChild, TemplateRef } from '@angular/core';
import { NgxDatatableModule, DatatableComponent } from '@swimlane/ngx-datatable';
import { TableColumn } from '@swimlane/ngx-datatable/release/types';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/Rx';
import { GridModel } from '../grid/grid-model.model'
@Injectable()
@Component({
selector: 'grid',
templateUrl: './grid.component.html'
})
export class GridComponent<T> implements OnInit, OnDestroy {
@Input()
columns: TableColumn[];
private _gridModelInput = new BehaviorSubject<GridModel<T>>(undefined);
@ViewChild('emptyTemplate')
public emptyTemplate: TemplateRef<any>;
@ViewChild('idAnchorEditTemplate')
public idAnchorEditTemplate: TemplateRef<any>;
@ViewChild('dateTemplate')
public dateTemplate: TemplateRef<any>;
@ViewChild('dateTimeTemplate')
public dateTimeTemplate: TemplateRef<any>;
// change data to use getter and setter
@Input()
set gridModelInput(value) {
// set the latest value for _data BehaviorSubject
if (value !== undefined) {
this._gridModelInput.next(value);
}
};
get gridModelInput() {
// get the latest value from _data BehaviorSubject
return this._gridModelInput.getValue();
}
@Output()
onFetchDataRequired = new EventEmitter<GridModel<T>>();
private gridModel: GridModel<T>;
private isLoading: boolean = false;
private currentPageLimit: number = 0;
private pageLimitOptions = [
{value: 10},
{value: 25},
{value: 50},
{value: 100},
];
constructor() {
}
ngOnInit(): void {
this.gridModel = new GridModel<T>();
this._gridModelInput.subscribe(gridModel => {
this.gridModel = gridModel;
this.isLoading = false;
}, err => console.log(err));
this.loadPage();
}
protected loadPage(pageEvent = {offset: 0}){
this.gridModel.CurrentPageNumber = pageEvent.offset;
this.onFetchDataRequired.emit(this.gridModel);
this.isLoading = true;
}
protected onSort(event) {
if (this.gridModel.SortBy != event.sorts[0].prop) {
//this means we are sorting on a new column
//so we need to return the paging to the first page
this.gridModel.CurrentPageNumber = 0;
}
this.gridModel.SortBy = event.sorts[0].prop;
this.gridModel.SortDir = event.sorts[0].dir;
this.loadPage();
}
public onLimitChange(limit: any): void {
this.gridModel.PageSize = this.currentPageLimit = parseInt(limit, 10);
this.gridModel.CurrentPageNumber = 0;
this.loadPage();
}
ngOnDestroy(): void {
this._gridModelInput.unsubscribe();
}
}
data-grid.component.html
<grid
(onFetchDataRequired)="fetchDataRequired($event)"
[gridModelInput]="gridModel">
</grid>
data-grid.component.ts
import { Component, OnInit, ViewChild, TemplateRef } from '@angular/core';
import { GridComponent } from '../shared/grid/grid.component';
import { GridModel } from '../shared/grid/grid-model.model';
import { DataGridRowModel } from './data-gridrow.model';
import { DataFetchService } from './data-fetch.service';
@Component({
templateUrl: 'data-grid.component.html'
})
export class DataGridComponent implements OnInit {
@ViewChild(GridComponent) grid: GridComponent<DataGridRowModel>;
gridModel: GridModel<DataGridRowModel> = new GridModel<DataGridRowModel>('DateCreated', 'desc');
ngOnInit(): void {
this.grid.columns = [
{ prop: 'Id', cellTemplate: this.grid.idAnchorEditTemplate, headerTemplate: this.grid.emptyTemplate }
, { prop: 'CreatedBy' }
, { prop: 'DateCreated', cellTemplate: this.grid.dateTimeTemplate, name: 'Created Date' }
];
}
constructor(private dataFetchService: DataFetchService) {
}
fetchDataRequired(gridModel: GridModel<DataGridRowModel>) {
this.dataFetchService
.getSortedPagedResults(gridModel)
.subscribe(gridModelResponse => {
this.gridModel = gridModelResponse;
});
}
}
The cool thing about it is, that it has commonly used templates pre-defined, e.g. for the id column (idAnchorEditTemplate
), date columns (dateTemplate
) or date/time columns (dateTimeTemplate
).
This allows maintenance of column templates that are used throughout the application in a single file.
One additional type that will be needed is GridModel:
export class GridModel<T> {
PageSize: number;
TotalElements: number;
TotalPages: number;
CurrentPageNumber: number;
SortBy: string;
SortDir: string;
Data: Array<T>;
constructor(defaultSortBy: string = 'Id', defaultSortDir: string = 'asc') {
this.PageSize = 10;
this.TotalElements = 0;
this.TotalPages = 0;
this.CurrentPageNumber = 0;
this.Data = new Array<T>();
this.SortBy = defaultSortBy;
this.SortDir = defaultSortDir;
}
}
Maybe someone benefit from it someday :)