User interface
Angular HTML Material SPA Typescript Visual Studio Code

How to Implement Pagination in an Angular Application

Welcome to today’s post.

Today I will show how to use the Angular Material library to implement a basic pagination component that can be used within a user interface component’s table of results.

The concept of pagination in user interfaces involves taking a large data set and displaying a fixed number of records from the data set in a grid. The starting record of each displayed set of records is determined by a paging control that allows the user to specify the page number or to increment/decrement the current page number.

In this post I will first give an overview of the Material Pagination Component before showing how to implement a pagination component in Angular.  

Overview of the Material Pagination Component

The Material library has a component, mat-paginator that can be used within our Angular application directly to page array data.

The component is quite straightforward to use and the following properties are quite important:

Property/MethodDescription
pageIndex The index of the currently selected page.
pageSize The size (number of records) of each page.
length The total number of records to be paged.
getNumberOfPages() Returns the number of pages.

Given the above properties we can implement a paginator that takes as input the current page, then displays the portion of the data source corresponding to the selected page. The user interface pattern we will utilize is shown below:

Angular pagination

The paginator component contains the paging control logic, state variables, and data sources that will allow us to generate the paged output based on selected page from the original data source. The generation of the paged view data source is done within the pagination component itself and output through an emitter event. The host user interface component then reads the emitted data source and binds it to the HTML grid.

The default page size is 10. Once the length (total number of items) is assigned, then the number of pages is computed by the paginator and is available from the getNumberOfPages() method.  

The minimum inputs to determine pagination are the length and pageSize.

Implementation of a Pagination Component

Given the pagination properties I have described in the previous section, then first part of the pagination component is the HTML template. The template includes the paginator element, <mat-paginator>, with the properties: length, pageSize, and pageSizeOptions.   

This is reflected in the component HTML template:

Pagination.component.html

<mat-paginator  #paginator
                [length]="totalItems"
                [pageSize]="itemsPerPage"
                [pageSizeOptions]="[5, 10, 25, 100]"
                (click)="onSelect($event)">
</mat-paginator>

In our component source we ensure that internal variables for the above paginator properties are declared and two additional properties to hold the data source and output paged data view.

Pagination.component.ts

Declare variables that will be used to store paginator properties, inputs and outputs:

import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from 
'@angular/core';
import { PageEvent } from '@angular/material';

@Component({
  selector: 'app-pagination',
  templateUrl: './pagination.component.html',
  styleUrls: ['./pagination.component.scss']
})
export class PaginationComponent implements OnInit {

  _currentPage: number = 0;
  _numberOfPages: number;
  _itemsPerPage: number = 10;
  _totalItems: number;
  _dataSource: any;
  _pagedOutput: any;

Declare template variable to access the paginator component declared with the #paginator template variable within HTML template view:

@ViewChild("paginator", {static: true}) paginator;

Once the component completes running ngOnAfterViewInit(), thepaginator variable will be populated with the default pagination data.

Setup input and output accessors for the items per page and total items to be paged:

@Input("itemsPerPage")
  set itemsPerPage(value: number) 
  { 
    this._itemsPerPage = value; 
    this.paginator.pageSize = value; 
  }

  @Input("totalItens")
  set totalItems(value: number) 
  { 
    this._totalItems = value; 
    this.paginator.length = value;  
  } 

Setup the input data source and output paged output that will contain the array of data that will be available to be displayed:

@Input("dataSource")
set dataSource(value: any) 
{ 
    this._dataSource = value; 
} 

@Output() pageChange: EventEmitter<PageEvent> = new EventEmitter();
@Output() pagedOutputChange: EventEmitter<any> = new EventEmitter();

Setup properties of the paginator component that can be accessed by host components:

get itemsPerPage() { return this._itemsPerPage; }
get numberOfPages() { return Math.ceil(this._totalItems / this._itemsPerPage); }
get currentPage() { return this._currentPage; }
get totalItems() { return this._totalItems; }

On selection of a page within the paginator component, the following event handler below will set component properties, emit the pageChange event, and recompute paged data view by calling the getPagedView() helper method:

onSelect(value: PageEvent)
{
    this._currentPage = this.paginator.pageIndex;
    this._numberOfPages = this.paginator.getNumberOfPages();
    this._itemsPerPage = this.paginator.pageSize;
    this.pageChange.emit(this.paginator);
    this.getPagedView();
}

The helper method that computes the paged data view uses the current page index and page size, then builds the paged data with all records that are in the current paging data window:

[pageIndex*pageSize, (pageIndex+1)*pageSize)

The outputs of the resulting computed paged data are then passed to the pagedOutputChange emitter.

getPagedView()
{
    this._pagedOutput = [];
    let itemIndex = 0;
    this._dataSource.forEach(element => {
        if ((itemIndex >= this.paginator.pageIndex*this.paginator.pageSize) 
         && (itemIndex < (this.paginator.pageIndex+1)*this.paginator.pageSize))
        {
            this._pagedOutput.push(element);
        }
        itemIndex++;
    }); 
    this.pagedOutputChange.emit(this._pagedOutput);
  }

To test our paging component, we can include it in a host component that contains a rendered list of records. I will show this in the next section.

Using the Pagination Component in a Host Component

Our host component displays a list of books and makes use of the pagination component to provide paging for the books record set.

In our host component we simply declare our paginator component using the selector:

app-pagination

with the required parameters that will initialise the component.

Books.component.html

<mat-card-content>
    <div *ngIf="hasLoaded$ | async; else loading">
        <div style="display: flex; flex-wrap: wrap; border-style: solid; 
         border-width: 1px; background-color: cadetblue;">
            <div style="flex: 0 40%;"><u><strong>TITLE</strong></u></div>
            <div style="flex: 0 40%;"><u><strong>AUTHOR</strong></u></div>
            <div style="flex: 0 20%;"><u><strong>&nbsp;</strong></u></div>
        </div>

        <mat-list *ngFor="let book of bookView">
            <div style="display: flex; flex-wrap: wrap; border-style: 
                 solid; border-width: 1px;">
                <div class="grid-row" style="flex: 0 40%;">{{book.title}}</div>
                <div class="grid-row" style="flex: 0 40%;">{{book.author}}</div>
                <div class="grid-row" style="flex: 0 20%;" class="clickLink" 
                    (click)="selectBookById(book.id)">
                    <u>VIEW</u>
                </div>
            </div> 
        </mat-list>

        <app-pagination 
            [itemsPerPage]="10" 
            [totalItens]="numberBooks"
            [dataSource]="books"
            (pageChange)="eventPageChanged($event)"
            (pagedOutputChange)="eventPagedOutputChanged($event)">
        </app-pagination>
    </div>

To be able to connect our pagination data with the table / grid data in our host component HTML template we need to declare two variables:

  1. A variable that will contain our table / grid record data and will be the data source for our pagination component.
  2. A variable that will be used to receive our pagination component’s page data output.

Books.component.ts

books: Book[];
bookView: Book[];
numberBooks = 0;

Variable books will be the pagination input data source.

Variable bookView will be the pagination output data view.

Below we show the initialization of both variables from a web API request: 

ngOnInit() {
    this.subscription = this.api.getBooks()
        .pipe(delay(this.apidelay))
        .subscribe(res => {
            this.hasLoaded$.next(true);
            this.books = res;
            this.bookView = res;
            this.numberBooks = this.bookView.length;
            console.log("read books from API");
        })
    }

Below are event handlers. The first one is for the page changed event.

eventPageChanged(event: any)
{
    console.log("page event firing!");
}

The second event handler eventPagedOutputChanged() method provides the paginator data view output from our components pagedOutputChange() emitter.

eventPagedOutputChanged(event: any)
{
    console.log("paginator output firing!");
    this.bookView = event;
}

Once we have assigned the emitted output data view to our grid data variable bookView, the HTML template binds each book type within a bookView array to elements within mat-list as shown:

<mat-list *ngFor="let book of bookView">
    <div style="display: flex; flex-wrap: wrap; border-style: solid; border-width: 1px;">
        <div class="grid-row" style="flex: 0 40%;">{{book.title}}</div>
        <div class="grid-row" style="flex: 0 40%;">{{book.author}}</div>
        <div class="grid-row" style="flex: 0 20%;" class="clickLink" 
            (click)="selectBookById(book.id)">
            <u>VIEW</u>
        </div>
    </div> 
</mat-list>

After the emitted output data view binds to our grid data variable bookView, the grid should display the recomputed page data as shown:

When the next page is clicked, the page will redisplay the next page of data:

We have seen how straightforward and effective the Angular paginator is to use either directly in your application UI components or as a reusable paginator component.

That’s all for today’s post.

I hope you found it useful and informative.

Social media & sharing icons powered by UltimatelySocial