Angular Bootstrap datatables
Angular Datatables - - Bootstrap 4 & Material Design
Angular Bootstrap Datatables are component which mixes tables with advanced options like searching, sorting, pagination and generating CSV file.
Basic example MDB Pro component Live Example
<div class="row">
<div class="col-md-6 mx-auto">
<div class="md-form">
<input type="text" class="form-control" [(ngModel)]="searchText" (keyup)="searchItems()" id="search-input"
mdbInputDirective>
<label for="search-input">Search</label>
</div>
</div>
<table mdbTable stickyHeader="true" hover="true" striped="true" class="z-depth-1">
<thead class="sticky-top">
<tr>
<th *ngFor="let head of headElements; let i = index" [mdbTableSort]="elements" [sortBy]="headElements[i]"
scope="col">{{head}} <mdb-icon icon="sort"></mdb-icon>
</th>
</tr>
</thead>
<tbody #row>
<tr mdbTableCol (rowCreated)="onRowCreate($event)" (rowRemoved)="onRowRemove($event)" *ngFor="let el of elements; let i = index">
<th *ngIf="i+1 >= firstItemIndex && i < lastItemIndex" scope="row">{{el.id}}</th>
<td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex" class="red-text">{{el.first}}</td>
<td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{el.last}}</td>
<td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{el.handle}}</td>
</tr>
</tbody>
<tfoot class="grey lighten-5 w-100">
<tr>
<td colspan="4">
<mdb-table-pagination paginationAlign="" [searchDataSource]="elements" (nextPageClick)="onNextPageClick($event)"
(previousPageClick)="onPreviousPageClick($event)"></mdb-table-pagination>
</td>
</tr>
</tfoot>
</table>
</div>
import { Component, OnInit, ElementRef, HostListener, AfterViewInit, ViewChild, ChangeDetectorRef } from '@angular/core';
import { MdbTableDirective, MdbTablePaginationComponent, MdbTableService } from 'ng-uikit-pro-standard';
@Component({
selector: 'app-datatables',
templateUrl: './datatables.component.html',
styleUrls: ['./datatables.component.scss']
})
export class DatatablesComponent implements OnInit, AfterViewInit {
@ViewChild(MdbTableDirective) mdbTable: MdbTableDirective;
@ViewChild(MdbTablePaginationComponent) mdbPagination: MdbTablePaginationComponent;
@ViewChild('row') row: ElementRef;
elements: any = [];
headElements = ['ID', 'First', 'Last', 'Handle'];
searchText: string = '';
previous: string;
firstItemIndex;
lastItemIndex;
constructor(private tableService: MdbTableService,
private cdRef: ChangeDetectorRef) {
}
@HostListener('input') oninput() {
this.mdbPagination.searchText = this.searchText;
}
ngOnInit() {
for (let i = 1; i <= 20; i++) {
this.elements.push({ id: i.toString(), first: 'Wpis ' + i, last: 'Last ' + i, handle: 'Handle ' + i });
}
this.tableService.setDataSource(this.elements);
this.elements = this.tableService.getDataSource();
this.previous = this.tableService.getDataSource();
}
ngAfterViewInit() {
this.mdbPagination.setMaxVisibleItemsNumberTo(3);
this.firstItemIndex = this.mdbPagination.firstItemIndex;
this.lastItemIndex = this.mdbPagination.lastItemIndex;
this.mdbPagination.calculateFirstItemIndex();
this.mdbPagination.calculateLastItemIndex();
this.cdRef.detectChanges();
}
addNewRow() {
// tslint:disable-next-line:max-line-length
this.tableService.addRow({ id: this.elements.length.toString(), first: 'Wpis ' + this.elements.length, last: 'Last ' + this.elements.length, handle: 'Handle ' + this.elements.length });
this.emitDataSourceChange();
}
addNewRowAfter() {
this.tableService.addRowAfter(1, { id: '2', first: 'Nowy', last: 'Row', handle: 'Kopytkowy' });
this.tableService.getDataSource().forEach((el, index) => {
el.id = (index + 1).toString();
});
this.emitDataSourceChange();
}
removeLastRow() {
this.tableService.removeLastRow();
this.emitDataSourceChange();
this.tableService.rowRemoved().subscribe((data) => {
console.log(data);
});
}
removeRow() {
this.tableService.removeRow(1);
this.tableService.getDataSource().forEach((el, index) => {
el.id = (index + 1).toString();
});
this.emitDataSourceChange();
this.tableService.rowRemoved().subscribe((data) => {
console.log(data);
});
}
emitDataSourceChange() {
this.tableService.dataSourceChange().subscribe((data: any) => {
console.log(data);
});
}
onNextPageClick(data: any) {
this.firstItemIndex = data.first;
this.lastItemIndex = data.last;
}
onPreviousPageClick(data: any) {
this.firstItemIndex = data.first;
this.lastItemIndex = data.last;
}
searchItems() {
const prev = this.tableService.getDataSource();
if (!this.searchText) {
this.tableService.setDataSource(this.previous);
this.elements = this.tableService.getDataSource();
}
if (this.searchText) {
this.elements = this.tableService.searchLocalDataBy(this.searchText);
this.tableService.setDataSource(prev);
}
this.mdbPagination.calculateFirstItemIndex();
this.mdbPagination.calculateLastItemIndex();
this.tableService.searchDataObservable(this.searchText).subscribe((data: any) => {
if (data.length === 0) {
this.firstItemIndex = 0;
}
});
}
}
Advanced table options
For advanced options of the tables have a look at specific documentation pages listed below.
Table pagination
Pagination is a simple navigation which lets you split a huge amount of content within the tables into smaller parts.
Table search
MDBootstrap search box enables super fast searching among all the data of the table.
Table sort
This functionality lets you sort the data of the tables according to any specific columns.
Table editable
Table editable allows you to edit existing data within the table and add new data to the table.
Table Responsive
Table responsive allows you to use tables on mobile devices.
Table styles
Table styles shows how you could customize your tables.
Angular Datatables - API
In this section you will find advanced information about the Table component. You will find out which modules are required in this component, what are the possibilities of configuring the component, and what events and methods you can use in working with it.
Modules used
In order to speed up your application, you can choose to import only the modules you actually need, instead of importing the entire MDB Angular library. Remember that importing the entire library, and immediately afterwards a specific module, is bad practice, and can cause application errors.
// MDB Angular Pro
import { WavesModule } from 'ng-uikit-pro-standard';
// Angular Forms
import { FormsModule } from '@angular/forms';
// Angular Http
import { HttpModule } from '@angular/http';
Exporting table data to CSV requires external library called
Angular5-csv
. Install it by typing following command into your console.
npm install angular5-csv --save
angular5-csv plugin
The table shows the configuration options of the angular5-csv library.
Name | Type | Default | Description | Example |
---|---|---|---|---|
fieldSeparator
|
string | ' , ' | Defines a character that separates fields in the generated .csv file |
fieldSeparator: ','
|
quoteStrings
|
string | ' , ' | If provided, will use this characters to "escape" fields, otherwise will use double quotes as deafult |
quoteStrings: '"'
|
decimalseparator
|
string | ' . ' | Defines the decimal separator character (default is .). If set to "locale", it uses the language sensitive representation of the number. |
decimalseparator: '.'
|
showLabels
|
boolean | false | If provided, would use this attribute to create a header row |
showLabels: true
|
showTitle
|
boolean | false | If provided, would use this attribute to create a title |
showTitle: true
|
useBom
|
boolean | true | If true, adds a BOM character at the start of the CSV |
useBom: true
|
noDownload
|
boolean | false | If true, disables automatic download and returns only formatted CSV |
noDownload: true,
|
headers
|
Array<string> | [''] | If provided, defines the column headers in CSV |
headers: ['Post ID', 'Post title', 'Post body']
|
Properties
The table shows the available properties in component.
Name | Type | Default | Description | Example |
---|---|---|---|---|
maxVisibleItems
|
number | 10 | If provided, defines the max visible items at one time. |
maxVisibleItems: number = 10
|
Methods
The table shows the available methods in component.
Name | Parameters | Description | Example |
---|---|---|---|
generateCsv()
|
searchData: any, title: string, options: Object | Generates a .csv file from the currently displayed table. It uses the source, file name, and configuration options as arguments. |
generateCsv() { new Angular5Csv(this.search(), 'data-table', this.options); }
|
Angular Datatables examples & customization
Here you can find a more advanced examples of Datatables component.
Datatables with modified items number MDB Pro component Live Example
Changed number of displayed objects in the table at once, and deleted possibility of generating CSV file.
<div class="card card-cascade narrower mt-5">
<!--Card image-->
<div class="view view-cascade gradient-card-header purple-gradient narrower py-4 mx-4 mb-3 d-flex justify-content-center align-items-center">
<h4 class="white-text font-weight-bold text-uppercase mb-0">Advanced table</h4>
</div>
<!--/Card image-->
<div class="row d-flex align-items-center justify-content-center">
<div class="col-md-6 mx-auto">
<div class="md-form">
<input type="search" [(ngModel)]="searchText" id="search" class="form-control" mdbInputDirective [mdbValidate]="false">
<label for="search">Search data</label>
</div>
</div>
</div>
<div class="px-2">
<!--Table-->
<table class="table table-hover table-responsive-md mb-0">
<!--Table head-->
<thead>
<tr>
<th style="width: 50px">id
<mdb-icon icon="sort" (click)="sortBy('id')"></mdb-icon>
</th>
<th class="th-lg">Post title
<mdb-icon icon="sort" (click)="sortBy('title')"></mdb-icon>
</th>
<th class="th-lg">Post body
<mdb-icon icon="sort" (click)="sortBy('body')"></mdb-icon>
</th>
</tr>
</thead>
<!--Table head-->
<!--Table body-->
<tbody>
<tr #list *ngFor="let data of search(); let i = index">
<th *ngIf="i+1 >= firstVisibleIndex && i+1 <= lastVisibleIndex" scope="row">{{data.id}}</th>
<td *ngIf="i+1 >= firstVisibleIndex && i+1 <= lastVisibleIndex">{{data.title}}</td>
<td *ngIf="i+1 >= firstVisibleIndex && i+1 <= lastVisibleIndex">{{data.body}}</td>
</tr>
</tbody>
<!--Table body-->
</table>
</div>
<hr class="my-0">
<!--Bottom Table UI-->
<div class="d-flex justify-content-center">
<!--Pagination -->
<nav class="my-4 pt-2">
<ul class="pagination pagination-circle pg-purple mb-0">
<!--First-->
<li class="page-item clearfix d-none d-md-block" (click)="firstPage()" [ngClass]="{disabled: activePage == firstPageNumber}">
<a class="page-link">First</a>
</li>
<!--Arrow left-->
<li class="page-item" (click)="previousPage($event)" [ngClass]="{disabled: activePage == firstPageNumber}">
<a class="page-link" aria-label="Previous">
<span aria-hidden="true">«</span>
<span class="sr-only">Previous</span>
</a>
</li>
<!--Numbers-->
<li *ngFor="let page of paginators; let i = index" class="page-item" [ngClass]="{active: i+1 == activePage}">
<a class="page-link waves-light" (click)="changePage($event)" mdbWavesEffect>{{page}}</a>
</li>
<!--Arrow right-->
<li class="page-item" (click)="nextPage($event)" [ngClass]="{disabled: activePage == lastPageNumber}">
<a class="page-link" aria-label="Next">
<span aria-hidden="true">»</span>
<span class="sr-only">Next</span>
</a>
</li>
<!--First-->
<li class="page-item clearfix d-none d-md-block" (click)="lastPage()" [ngClass]="{disabled: activePage == lastPageNumber}">
<a class="page-link">Last</a>
</li>
</ul>
</nav>
<!--/Pagination -->
</div>
<!--Bottom Table UI-->
</div>
import { Component, OnInit, HostListener, ViewChildren, QueryList, ElementRef } from '@angular/core';
import { Http } from '@angular/http';
@Component({
selector: 'app-datatables',
templateUrl: './datatables.component.html',
styleUrls: ['./datatables.component.scss'],
})
export class AppDatatablesComponent implements OnInit {
@ViewChildren('list') list: QueryList<ElementRef>;
paginators: Array<any> = [];
activePage: number = 1;
firstVisibleIndex: number = 1;
lastVisibleIndex: number = 10;
url: any = 'https://jsonplaceholder.typicode.com/posts';
tableData = [];
sorted = false;
searchText: string;
firstPageNumber: number = 1;
lastPageNumber: number;
maxVisibleItems: number = 20;
constructor(private http: Http) { }
getData() {
return this.http.get(this.url);
}
ngOnInit() {
this.getData().subscribe((next: any) => {
next.json().forEach((element: any) => {
this.tableData.push({ id: (element.id).toString(), title: element.title, body: element.body });
});
});
setTimeout(() => {
for (let i = 1; i <= this.tableData.length; i++) {
if (i % this.maxVisibleItems === 0) {
this.paginators.push(i / this.maxVisibleItems);
}
}
if (this.tableData.length % this.paginators.length !== 0) {
this.paginators.push(this.paginators.length + 1);
}
this.lastPageNumber = this.paginators.length;
this.lastVisibleIndex = this.maxVisibleItems;
}, 200);
}
@HostListener('input') oninput() {
this.paginators = [];
for (let i = 1; i <= this.search().length; i++) {
if (!(this.paginators.indexOf(Math.ceil(i / this.maxVisibleItems)) !== -1)) {
this.paginators.push(Math.ceil(i / this.maxVisibleItems));
}
}
this.lastPageNumber = this.paginators.length;
}
changePage(event: any) {
if (event.target.text >= 1 && event.target.text <= this.maxVisibleItems) {
this.activePage = +event.target.text;
this.firstVisibleIndex = this.activePage * this.maxVisibleItems - this.maxVisibleItems + 1;
this.lastVisibleIndex = this.activePage * this.maxVisibleItems;
}
}
nextPage() {
this.activePage += 1;
this.firstVisibleIndex = this.activePage * this.maxVisibleItems - this.maxVisibleItems + 1;
this.lastVisibleIndex = this.activePage * this.maxVisibleItems;
}
previousPage() {
this.activePage -= 1;
this.firstVisibleIndex = this.activePage * this.maxVisibleItems - this.maxVisibleItems + 1;
this.lastVisibleIndex = this.activePage * this.maxVisibleItems;
}
firstPage() {
this.activePage = 1;
this.firstVisibleIndex = this.activePage * this.maxVisibleItems - this.maxVisibleItems + 1;
this.lastVisibleIndex = this.activePage * this.maxVisibleItems;
}
lastPage() {
this.activePage = this.lastPageNumber;
this.firstVisibleIndex = this.activePage * this.maxVisibleItems - this.maxVisibleItems + 1;
this.lastVisibleIndex = this.activePage * this.maxVisibleItems;
}
sortBy(by: string | any): void {
if (by == 'id') {
this.search().reverse();
} else {
this.search().sort((a: any, b: any) => {
if (a[by] < b[by]) {
return this.sorted ? 1 : -1;
}
if (a[by] > b[by]) {
return this.sorted ? -1 : 1;
}
return 0;
});
}
this.sorted = !this.sorted;
}
filterIt(arr: any, searchKey: any) {
return arr.filter((obj: any) => {
return Object.keys(obj).some((key) => {
return obj[key].includes(searchKey);
});
});
}
search() {
if (!this.searchText) {
return this.tableData;
}
if (this.searchText) {
return this.filterIt(this.tableData, this.searchText);
}
}
}
Datatables with data from remote API MDB Pro component Live Example
Datatables with data fetching from real online API.
<div class="container">
<div class="row">
<div class="col-md-6 mx-auto">
<div class="md-form">
<input type="text" class="form-control" [(ngModel)]="searchText" (keyup)="searchItems()" id="search-input"
mdbInputDirective>
<label for="search-input">Search</label>
</div>
</div>
<table mdbTable stickyHeader="true" hover="true" striped="true" class="z-depth-1">
<thead class="sticky-top">
<tr>
<th *ngFor="let head of headElements; let i = index" [mdbTableSort]="elements" [sortBy]="headElements[i]"
scope="col">{{head}} <mdb-icon icon="sort"></mdb-icon>
</th>
</tr>
</thead>
<tbody #row>
<tr mdbTableCol (rowCreated)="onRowCreate($event)" (rowRemoved)="onRowRemove($event)" *ngFor="let el of elements; let i = index">
<th *ngIf="i+1 >= firstItemIndex && i < lastItemIndex" scope="row">{{el.id}}</th>
<td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex" class="red-text">{{el.first}}</td>
<td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{el.last}}</td>
<td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{el.handle}}</td>
</tr>
</tbody>
<tfoot class="grey lighten-5 w-100">
<tr>
<td colspan="4">
<mdb-table-pagination paginationAlign="" [searchDataSource]="elements" (nextPageClick)="onNextPageClick($event)"
(previousPageClick)="onPreviousPageClick($event)"></mdb-table-pagination>
</td>
</tr>
</tfoot>
</table>
</div>
<button mdbBtn color="primary" (click)="set()">Set Data</button>
</div>
import { HttpClient } from '@angular/common/http';
import { Component, OnInit, ElementRef, HostListener, AfterViewInit, ViewChild, ChangeDetectorRef } from '@angular/core';
import { MdbTableDirective, MdbTablePaginationComponent, MdbTableService } from 'ng-uikit-pro-standard';
@Component({
selector: 'data-from-api',
templateUrl: './data-from-api.component.html',
styleUrls: ['./data-from-api.component.scss'],
})
export class DataFromApiComponent implements OnInit, AfterViewInit {
@ViewChild(MdbTableDirective) mdbTable: MdbTableDirective;
@ViewChild(MdbTablePaginationComponent) mdbPagination: MdbTablePaginationComponent;
@ViewChild('row') row: ElementRef;
elements: any = [];
headElements = ['ID', 'First', 'Last', 'Handle'];
searchText: string = '';
previous: string;
firstItemIndex;
lastItemIndex;
url = 'https://jsonplaceholder.typicode.com/posts';
constructor(
private tableService: MdbTableService,
private cdRef: ChangeDetectorRef,
private http: HttpClient) { }
@HostListener('input') oninput() {
this.mdbPagination.searchText = this.searchText;
}
ngOnInit() {
this.http.get(this.url).subscribe((data: any) => {
data.forEach((el: any) => {
this.elements.push({
id: el.id.toString(),
first: el.title,
last: el.body,
handle: 'Handle ' + el.id.toString()
});
});
this.tableService.setDataSource(this.elements);
});
this.elements = this.tableService.getDataSource();
this.previous = this.tableService.getDataSource();
}
ngAfterViewInit() {
this.mdbPagination.setMaxVisibleItemsNumberTo(10);
this.firstItemIndex = this.mdbPagination.firstItemIndex;
this.lastItemIndex = this.mdbPagination.lastItemIndex;
this.mdbPagination.calculateFirstItemIndex();
this.mdbPagination.calculateLastItemIndex();
this.cdRef.detectChanges();
}
onNextPageClick(data: any) {
this.firstItemIndex = data.first;
this.lastItemIndex = data.last;
}
onPreviousPageClick(data: any) {
this.firstItemIndex = data.first;
this.lastItemIndex = data.last;
}
searchItems() {
const prev = this.tableService.getDataSource();
if (!this.searchText) {
this.tableService.setDataSource(this.previous);
this.elements = this.tableService.getDataSource();
}
if (this.searchText) {
this.elements = this.tableService.searchLocalDataBy(this.searchText);
this.tableService.setDataSource(prev);
}
this.mdbPagination.calculateFirstItemIndex();
this.mdbPagination.calculateLastItemIndex();
this.tableService.searchDataObservable(this.searchText).subscribe((data: any) => {
if (data.length === 0) {
this.firstItemIndex = 0;
}
});
}
}