Server-Side Pagination Using Angular 8 and Node.js

Server-Side Pagination Using Angular 8 and Node.js

You'll learn how to implement server-side pagination in Angular 8 with a Node.js backend API.

Server-side pagination is very useful when we are dealing with huge amounts of data. When there's a lot of data, client-side pagination will take a long time to get all the data to the same time, so it's better to make a server call on every page request.

This is a simple example of how to implement server-side pagination in Angular 8 with a Node.js backend API.

The example contains a hard coded array of 150 objects split into 30 pages (5 items per page) to demonstrate how the pagination logic works. Styling of the example is done with Bootstrap 4.

Running the Angular + Node Pagination Example Locally
  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download or clone the tutorial project source code from https://github.com/cornflourblue/angular-8-node-server-side-pagination.
  3. Install required npm packages of the backend Node API by running the npm install command in the /server folder.
  4. Start the backend Node API by running npm start in the /server folder, this will start the API on the URL http://localhost:4000.
  5. Install required npm packages of the frontend Angular app by running the npm install command in the /client folder.
  6. Start the Angular frontend app by running npm start in the /client folder, this will build the app with webpack and automatically launch it in a browser on the URL http://localhost:8080.
Server-Side (Node.js) Pagination Logic

Pagination is handled by the backend Node API with the help of the jw-paginate npm package.

Below is the code for the paged items route (/api/items) in the node server file (/server/server.js) in the example, it creates a hardcoded list of 150 items to be paged, in a real application you would replace this with real data (e.g. from a database). The route accepts an optional page parameter in the url query string, if the parameter isn't set it defaults to the first page.

The paginate() function is from the jw-paginate package and accepts the following parameters:

  • totalItems (required) - the total number of items to be paged
  • currentPage (optional) - the current active page, defaults to the first page
  • pageSize (optional) - the number of items per page, defaults to 10
  • maxPages (optional) - the maximum number of page navigation links to display, defaults to 10

The output of the paginate function is a pager object containing all the information needed to get the current pageOfItems out of the items array, and to display the pagination controls in the Angular frontend, including:

  • startIndex - the index of the first item of the current page (e.g. 0)
  • endIndex - the index of the last item of the current page (e.g. 9)
  • pages - the array of page numbers to display (e.g. [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ])
  • currentPage - the current active page (e.g. 1)
  • totalPages - the total number of pages (e.g. 30)

I've set the pageSize to 5 in the CodeSandbox example above so the pagination links aren't hidden below the terminal console when the container starts up. In the code on GitHub I didn't set the page size so the default 10 items are displayed per page in that version.

The current pageOfItems is extracted from the items array using the startIndex and endIndex from the pager object. The route then returns the pager object and current page of items in a JSON response.

// paged items route
app.get('/api/items', (req, res, next) => {
    // example array of 150 items to be paged
    const items = [...Array(150).keys()].map(i => ({ id: (i + 1), name: 'Item ' + (i + 1) }));

    // get page from query params or default to first page
    const page = parseInt(req.query.page) || 1;

    // get pager object for specified page
    const pageSize = 5;
    const pager = paginate(items.length, page, pageSize);

    // get page of items from items array
    const pageOfItems = items.slice(pager.startIndex, pager.endIndex + 1);

    // return pager object and current page of items
    return res.json({ pager, pageOfItems });
});
Client-Side (Angular 8) Pagination Components

Since the pagination logic is handled on the server, the only thing the Angular client needs to do is fetch the pager information and current page of items from the backend, and display them to the user.

Angular App Component

Below is the Angular app component (/client/src/app/app.component.ts) from the example. The setPage() method fetches the pager object and pageOfItems for the specified page from the backend API with an HTTP request, and the constructor() sets the initial page to 1 when the component first loads.

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent {
    pager = {};
    pageOfItems = [];

    constructor(private http: HttpClient) {
        // start on page 1
        this.setPage(1);
    }

    setPage(page: number) {
        // get new pager object and page of items from the api
        this.http.get(`/api/items?page=${page}`)
            .subscribe(response => {
                this.pager = response.pager;
                this.pageOfItems = response.pageOfItems;
            });
    }
}

Angular App Component Template

The app component template (/client/src/app/app.component.html) renders the current page of items as a list of divs with the *ngFor directive, and renders the pagination controls using the data from the pager object.

The CSS classes used are all part of Bootstrap 4.3, for more info see https://getbootstrap.com/docs/4.3/getting-started/introduction/.

<div class="card text-center m-3">
    <h3 class="card-header">Angular 8 + Node - Server Side Pagination Example</h3>
    <div class="card-body">
        <div *ngFor="let item of pageOfItems">{{item.name}}</div>
    </div>
    <div class="card-footer pb-0 pt-3">
        <ul *ngIf="pager.pages && pager.pages.length" class="pagination">
            <li [ngClass]="{disabled:pager.currentPage === 1}" class="page-item first-item">
                <a (click)="setPage(1)" class="page-link">First</a>
            </li>
            <li [ngClass]="{disabled:pager.currentPage === 1}" class="page-item previous-item">
                <a (click)="setPage(pager.currentPage - 1)" class="page-link">Previous</a>
            </li>
            <li *ngFor="let page of pager.pages" [ngClass]="{active:pager.currentPage === page}"
                class="page-item number-item">
                <a (click)="setPage(page)" class="page-link">{{page}}</a>
            </li>
            <li [ngClass]="{disabled:pager.currentPage === pager.totalPages}" class="page-item next-item">
                <a (click)="setPage(pager.currentPage + 1)" class="page-link">Next</a>
            </li>
            <li [ngClass]="{disabled:pager.currentPage === pager.totalPages}" class="page-item last-item">
                <a (click)="setPage(pager.totalPages)" class="page-link">Last</a>
            </li>
        </ul>
    </div>
</div>

The tutorial code is available on GitHub

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading

Angular 8 (formerly Angular 2) - The Complete Guide

Angular & NodeJS - The MEAN Stack Guide

The Complete Node.js Developer Course (3rd Edition)

The Web Developer Bootcamp

Best 50 Angular Interview Questions for Frontend Developers in 2019

MEAN Stack Angular 8 CRUD Web Application

Angular 8 Tutorial - User Registration and Login Example

How to build a CRUD Web App with Angular 8.0

Building CRUD Mobile App using Ionic 4, Angular 8

Angular 8 Material Design Tutorial & Example