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 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">&laquo;</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">&raquo;</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;
                }

              });
            }
          }