Angular Material Data Table Paginator with Server Side Pagination

Angular Material Desing components provide modern UI, optimized performance and lots of powerful feature with easy implementation. In this post, we are going through an example of how to use the Angular Material Data Table and Material Paginator for server-side pagination.

Let's get started.

Simple API with Pagination

In this example, we will be using node js to create a simple API with pagination.
Here is a simple node application, which response list of users. The users.js file contains an array of data for users with id, name and age. the /users endpoint is already configured for pagination, but without any query, it will response all the data.
Hello Write some things here
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const express = require('express');

const Users = require('./users.js');

const app = express();

app.use((req,res,next)=>{
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});

app.get('/users', (req,res,next) =>{
console.log("Get Users");
let offset = parseInt(req.query.offset) || 0;
let size = parseInt(req.query.limit) || Users.length;

let from = offset * size;
let to = from + size;

let users = Users.slice(from, to);
res.status(200).json({
total:Users.length,
users
});
});

app.listen(3000);
console.log("Server Started at http://localhost:3000");

You can find the code in this GitHub repo.

Angular Setup

As you are looking for the server-side pagination on the material table, I believe you have all the basic understanding required to set up a table with datasource and paginator. So let not waste any time here. Here is a table with mat paginator
app.component.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { Component, ViewChild } from '@angular/core';

import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { HttpClient } from '@angular/common/http';


@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {

users: any[];
loading: boolean = true;
dataSource = new MatTableDataSource<any>();

title = 'pagination';

@ViewChild(MatPaginator) paginator: MatPaginator;

constructor(private http: HttpClient) {

}

ngOnInit() {
this.getData();
}

getData(){
this.http.get(`http://localhost:3000/users`).subscribe((response: any) =>{

this.loading = false;
this.users = response.users;
this.dataSource = new MatTableDataSource<any>(this.users);
this.dataSource.paginator = this.paginator;

})
}
}
and HTML for the component
app.component.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<div>
<mat-card>
<mat-card-header>
<mat-card-title>
Server Side Pagination in angular
</mat-card-title>
</mat-card-header>
</mat-card>

<mat-paginator
[pageSizeOptions]="[5, 10, 15, 20, 30]"
showFirstLastButtons>
</mat-paginator>
<div *ngIf="loading;else table">
<mat-spinner class="center"></mat-spinner>
</div>

<ng-template #table>
<table mat-table [dataSource]="dataSource">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef> Id </th>
<td mat-cell *matCellDef="let element"> {{element.id}} </td>

</ng-container>

<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>

<ng-container matColumnDef="age">
<th mat-header-cell *matHeaderCellDef> Age </th>
<td mat-cell *matCellDef="let element"> {{element.age}} </td>
</ng-container>


<tr mat-header-row *matHeaderRowDef="['id', 'name', 'age']"></tr>
<tr mat-row *matRowDef="let row; columns: ['id', 'name', 'age'];"></tr>
</table>
</ng-template>
</div>

With this Setup, we fetch all the users data when the component initialized and use mat-paginator to display with pagination.
Mat-Paginator in Table, Angular Material


Implementing Serverside Pagination with Paginator

Now Let's talk about the API, we had created at the beginning of the article. The /users endpoint accepts two query offset and limit. And in every response, it sends back the total no of data. So that we can understand till when to request got the next data.



Offset is the position in the dataset of a particular record. and the limit is the required data length.
http://localhost:3000/users?offset=1&limit=5
Here offset = 1, so 5 users are already loaded. requesting for next users from index 6 to 10 as limit = 5;

How can we use the mat-paginator to fetch the next data as required? mat paginator docs link.
Going through the documentation we know that it emits an event page When the page is changed. We can make use to that.

First, when the data is loaded we have only 5 users, so the next page is blocked. We can set the length to the total from the server before assigning the data to datasource, in this way the mat paginator behaves like it has all the data. Got it? This is the core of this post.
In simple word users.length = response.total This will add a null value for every data that is not available.

And in every page event, we make another API call and add the data in the required index. The event provides all the necessary data index, size and previous index.

Here is the final code
app.component.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import { Component, ViewChild } from '@angular/core';

import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { HttpClient, HttpParams } from '@angular/common/http';


@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {

users: any[];
loading: boolean = true;
dataSource = new MatTableDataSource<any>();

title = 'pagination';

@ViewChild(MatPaginator) paginator: MatPaginator;

constructor(private http: HttpClient) {

}

ngOnInit() {
this.getData('0', '5');
}

getData(offset, limit){
let params = new HttpParams();
params = params.set('offset', offset);
params = params.set('limit', limit);

this.http.get('http://localhost:3000/users?' + params.toString())
.subscribe((response: any) =>{

this.loading = false;
this.users = response.users;
this.users.length = response.total;

this.dataSource = new MatTableDataSource<any>(this.users);
this.dataSource.paginator = this.paginator;

})
}

getNextData(currentSize, offset, limit){
let params = new HttpParams();
params = params.set('offset', offset);
params = params.set('limit', limit);

this.http.get('http://localhost:3000/users?' + params.toString())
.subscribe((response: any) =>{

this.loading = false;

this.users.length = currentSize;
this.users.push(...response.users);

this.users.length = response.total;

this.dataSource = new MatTableDataSource<any>(this.users);
this.dataSource._updateChangeSubscription();

this.dataSource.paginator = this.paginator;

})
}

pageChanged(event){
this.loading = true;

let pageIndex = event.pageIndex;
let pageSize = event.pageSize;

let previousIndex = event.previousPageIndex;

let previousSize = pageSize * pageIndex;

this.getNextData(previousSize, (pageIndex).toString(), pageSize.toString());
}
}

getNextData(currentSize, offset, limit) is the methods you are interested in. As we had already set the length to total, pushing additional data will add it after the index (living those null value in the middle) so we should set back the size to currentSize and push data and set its size to the total.


Mat-Paginator with server side pagination, Data Table Angular


You can find the source code in this GitHub repo

That's all. You implement server-side pagination with material paginator in the material data table.

6 Comments

  1. Thank you mate.. you saved my lot of efforts.

    ReplyDelete
  2. Thanks for the example! One correction: your code in github is correct, but on the web page, you need to add (page)="pageChanged($event)" to the mat-paginator element in the HTML.

    ReplyDelete
  3. Well !! great.. now i gotta do the same in react...

    ReplyDelete