Angular 2 material implementing sort, filter and pagination

15,291

Solution 1

Edit on 01/29/2018 : an article about the material data table has been written and can be seen here, it includes server pagination, filtering and sorting and is probably more up-to-date than my code below, since a lot of people are visiting this post I guess there was a real need for this, hopefully it will help you all, have fun.

Edit: the plunkr example does not work anymore so I reimplemented it on stackblitz with the latest material release (as of this edit, beta 12) note : if you are using an older version of material you will need to change the mat prefixes back to md

Here is the updated version

This plunkr example (does not work anymore, see link above for a working version) will show you the table with all the implementations you are looking for.

You can see how it is implemented with the dependency injection :

    const displayDataChanges = [
  this._exampleDatabase.dataChange,
  this._sort.mdSortChange, 
  this._filterChange,
  this._paginator.page,
];

The Observable.merge(...displayDataChanges).map filters the data, the sortData() method sorts it and the paginator generates the number of pages depending on the length of the filtered data.

Solution 2

This is what use for pagination and sorting :

import { Component, OnInit } from '@angular/core';
import { ViewChild, Output, EventEmitter } from '@angular/core';
import { DataSource } from '@angular/cdk';
import { MdPaginator, MdSort } from '@angular/material';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import { SharedService } from "app/services/shared.service";

@Component({
  selector: 'app-rtable',
  styleUrls: ['./rtable.component.css'],
  templateUrl: './rtable.component.html',
})
export class RtableComponent {
  displayedColumns = ['userId', 'userName', 'progress', 'color', 'url'];
  exampleDatabase = new ExampleDatabase();
  dataSource: ExampleDataSource | null;

  @ViewChild(MdPaginator) paginator: MdPaginator;
  @ViewChild(MdSort) sort: MdSort;
  @Output()
  playAudioPlayer: EventEmitter<string> = new EventEmitter();

  constructor(private _sharedService: SharedService) { }

  ngOnInit() {
 
  }
}

const COLORS = ['maroon', 'red', 'orange', 'yellow', 'olive', 'green', 'purple',
  'fuchsia', 'lime', 'teal', 'aqua', 'blue', 'navy', 'black', 'gray'];
const NAMES = ['Maia', 'Asher', 'Olivia', 'Atticus', 'Amelia', 'Jack',
  'Charlotte', 'Theodore', 'Isla', 'Oliver', 'Isabella', 'Jasper',
  'Cora', 'Levi', 'Violet', 'Arthur', 'Mia', 'Thomas', 'Elizabeth'];
const URLS = ['http://localhost/ragnar1.mp3', 'http://localhost/ragnar1.mp3',
  'http://localhost/ragnar1.mp3', 'http://localhost/ragnar1.mp3',
  'http://localhost/ragnar2.mp3', 'http://localhost/ragnar2.mp3',
  'http://localhost/ragnar3.mp3', 'http://localhost/ragnar3.mp3',
  'http://localhost/ragnar1.mp3', 'http://localhost/ragnar1.mp3',
  'http://localhost/ragnar2.mp3', 'http://localhost/ragnar2.mp3',
  'http://localhost/ragnar3.mp3', 'http://localhost/ragnar3.mp3',
  'http://localhost/ragnar1.mp3', 'http://localhost/ragnar1.mp3',
  'http://localhost/ragnar2.mp3', 'http://localhost/ragnar2.mp3',
  'http://localhost/ragnar3.mp3'];

export interface UserData {
  id: string;
  name: string;
  progress: string;
  color: string;
  url: string;
}
export class ExampleDatabase {
  /** Stream that emits whenever the data has been modified. */
  dataChange: BehaviorSubject<UserData[]> = new BehaviorSubject<UserData[]>([]);
  get data(): UserData[] { return this.dataChange.value; }

  constructor() {
    // Fill up the database with 100 users.
    for (let i = 0; i < 100; i++) { this.addUser(); }
  }

  /** Adds a new user to the database. */
  addUser() {
    const copiedData = this.data.slice();
    copiedData.push(this.createNewUser());
    this.dataChange.next(copiedData);
  }

  /** Builds and returns a new User. */
  private createNewUser() {
    const name =
      NAMES[Math.round(Math.random() * (NAMES.length - 1))] + ' ' +
      NAMES[Math.round(Math.random() * (NAMES.length - 1))].charAt(0) + '.';

    return {
      id: (this.data.length + 1).toString(),
      name: name,
      progress: Math.round(Math.random() * 100).toString(),
      color: COLORS[Math.round(Math.random() * (COLORS.length - 1))],
      url: URLS[Math.round(Math.random() * (URLS.length - 1))]
    };
  }
}

export class ExampleDataSource extends DataSource<any> {
  constructor(private _exampleDatabase: ExampleDatabase, private _paginator: MdPaginator, private _sort: MdSort) {
    super();
  }

  /** Connect function called by the table to retrieve one stream containing the data to render. */
  connect(): Observable<UserData[]> {
    const displayDataChanges = [
      this._exampleDatabase.dataChange,
      this._paginator.page,
      this._sort.mdSortChange
    ];

    return Observable.merge(...displayDataChanges).map(() => {
      const data = this.getSortedData();

      // Grab the page's slice of data.
      const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
      return data.splice(startIndex, this._paginator.pageSize);
    });
  }

  disconnect() { }

  /** Returns a sorted copy of the database data. */
  getSortedData(): UserData[] {
    const data = this._exampleDatabase.data.slice();
    if (!this._sort.active || this._sort.direction == '') { return data; }

    return data.sort((a, b) => {
      let propertyA: number | string = '';
      let propertyB: number | string = '';

      switch (this._sort.active) {
        case 'userId': [propertyA, propertyB] = [a.id, b.id]; break;
        case 'userName': [propertyA, propertyB] = [a.name, b.name]; break;
        case 'progress': [propertyA, propertyB] = [a.progress, b.progress]; break;
        case 'color': [propertyA, propertyB] = [a.color, b.color]; break;
        case 'url': [propertyA, propertyB] = [a.url, b.url]; break;
      }

      let valueA = isNaN(+propertyA) ? propertyA : +propertyA;
      let valueB = isNaN(+propertyB) ? propertyB : +propertyB;

      return (valueA < valueB ? -1 : 1) * (this._sort.direction == 'asc' ? 1 : -1);
    });
  }


}
/* Structure */
.example-container {
  display: flex;
  flex-direction: column;
  max-height: 500px;
  min-width: 300px;
}

.example-header {
  min-height: 64px;
  display: flex;
  align-items: center;
  padding-left: 24px;
  font-size: 20px;
}

.mat-table {
  overflow: auto;
}
md-row:hover{
  background-color: #f3f3f3;
}
<div class="example-container mat-elevation-z8">

  <md-table #table mdSort [dataSource]="dataSource">

    <!--- Note that these columns can be defined in any order.
          The actual rendered columns are set as a property on the row definition" -->
    <md-header-row *cdkHeaderRowDef="displayedColumns"></md-header-row>
    <md-row *cdkRowDef="let row; columns: displayedColumns;"></md-row>

    <!-- ID Column -->
    <ng-container cdkColumnDef="userId">
      <md-header-cell *cdkHeaderCellDef md-sort-header> ID </md-header-cell>
      <md-cell *cdkCellDef="let row"> {{row.id}} </md-cell>
    </ng-container>

    <!-- Progress Column -->
    <ng-container cdkColumnDef="progress">
      <md-header-cell *cdkHeaderCellDef md-sort-header> Progress </md-header-cell>
      <md-cell *cdkCellDef="let row"> {{row.progress}}% </md-cell>
    </ng-container>

    <!-- Name Column -->
    <ng-container cdkColumnDef="userName">
      <md-header-cell *cdkHeaderCellDef md-sort-header> Name </md-header-cell>
      <md-cell *cdkCellDef="let row"> {{row.name}} </md-cell>
    </ng-container>

    <!-- Color Column -->
    <ng-container cdkColumnDef="color">
      <md-header-cell *cdkHeaderCellDef md-sort-header> Color </md-header-cell>
      <md-cell *cdkCellDef="let row" [style.color]="row.color"> {{row.color}} </md-cell>
    </ng-container>

    <!-- Url Column -->
    <ng-container cdkColumnDef="url">
      <md-header-cell *cdkHeaderCellDef md-sort-header> Audio </md-header-cell>
      <md-cell *cdkCellDef="let row" >
         <button md-button (click)="callAudioEvent(row.url)" [style.color]="row.color">Listen..</button>
        </md-cell>
    </ng-container>

  </md-table>

  <md-paginator #paginator [length]="exampleDatabase.data.length" [pageIndex]="0" [pageSize]="5" [pageSizeOptions]="[5, 10, 25,50, 100]">
  </md-paginator>
</div>
Share:
15,291
Admin
Author by

Admin

Updated on June 05, 2022

Comments

  • Admin
    Admin about 2 years

    I'm trying to implement sort, filter and pagination using md-table. here is my code:

    connect(): Observable<Patient[]> {
    const displayPatientDataChanges = [
      this._patientDatabase.dataChange,
      this._filterPatientChange,
      this._paginator.page,
      this._sort.mdSortChange,
    ];
    
    return Observable.merge(...displayPatientDataChanges).map(() => {
      const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
    
      let displayData = this._patientDatabase.data.slice().filter((item: Patient) => {
        let searchStr = (item.firstname + ' ' + item.lastname).toLowerCase();
        return searchStr.indexOf(this.filter.toLowerCase()) != -1;
      });
    

    I want to return both of these values but it only returns the sort function the filter and the pagination don't work.

      return displayData.splice(startIndex, this._paginator.pageSize),this.getSortedData();
    
    
    });
    
    }
    
     disconnect() { }
    
    getSortedData(): Patient[] {
    const data = this._patientDatabase.data.slice();
    if (!this._sort.active || this._sort.direction == '') { return data; }
    
    return data.sort((a, b) => {
      let propertyA: number|string|Date = '';
      let propertyB: number|string|Date = '';
    
      switch (this._sort.active) {
        case 'id': [propertyA, propertyB] = [a.id, b.id]; break;
        case 'firstname': [propertyA, propertyB] = [a.firstname, b.firstname]; break;
        case 'lastname': [propertyA, propertyB] = [a.lastname, b.lastname]; break;
        case 'dateOfBirth': [propertyA, propertyB] = [a.dateOfBirth, b.dateOfBirth]; break;
        case 'sex': [propertyA, propertyB]= [a.sex, b.sex]; break;
        case 'dateAdded': [propertyA, propertyB] = [a.dateAdded, b.dateAdded]; break;
      }
    
      let valueA = isNaN(+propertyA) ? propertyA : +propertyA;
      let valueB = isNaN(+propertyB) ? propertyB : +propertyB;
    
      return (valueA < valueB ? -1 : 1) * (this._sort.direction == 'asc' ? 1 : 1);
    });
     }
    

    How can I make the sort, filter and pagination to work?