User interface
Angular HTML Material SPA Typescript Visual Code

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 UI component’s table of results.

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 UI pattern we will utilise is shown below:

Angular pagination

Within the paginator component contains the paginator component, 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 UI 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. 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);
  }

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 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;
}

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