Angular 10 Server Side Pagination in Nodejs MongoDB Example – using Express RestAPIs + Mongoose ODM Tutorial

Angular 10 Nodejs MongoDB Pagination (Frontend and Server side)RestAPIs

In the tutorial, I introduce how to build an “Angular 10 Server Side Pagination in Nodejs MongoDB Example” project using Express framework and Mongoose queries to interact with MongoDB database’s documents.

– Nodejs Express project (server side pagination) produces pagination RestAPIs with MongoDB database documents using Mongoose queries.
– Angular 10 project (client side pagination) will consume the Node.js pagination RestAPIs then show up on component’s views.

Related posts:



– I draw a fullstack overview Diagram Architecture from Angular Frontend to MongoDB database through Nodejs RestAPI backend.
– Develop Nodejs Pagination Filtering and Sorting RestAPIs with the supporting of Express Rest Framework.
– Implement Angular CRUD application with the Angular Httpclient to do http request (Post/Get/Put/Delete) to Nodejs Backend APIs.
– I create a testsuite with a number of integrative testcases with CRUD RestAPI requests from Angular 10 HttpClient to do CRUD requests to Nodejs RestAPIs Server and save/retrieve data to MongoDB database.

Overview Angular 10 Server Side Pagination in Nodejs MongoDB Example

Diagram Architecture Design: Angular Nodejs MongoDB Application

Angular 10 Nodejs MongoDB Diagram Architecture
Angular 10 Nodejs MongoDB Diagram Architecture

In the tutorial, We develop 2 projects:

  • Backend Project – Nodejs MongoDB Pagination Application gets data from MongoDB database then provides RestAPIs with pagination, filtering and sorting function for frontend
  • Frontend Project – Angular Application use HttpClient to fetch data from Backend Application then shows them in Bootstrap table with pagination, filtering and sorting functions

Tutorial Goals with Integrative testing

– Make a request at API: /api/customers/pagefiltersort with pagination, filtering and sorting params as below:

  • page: 0 – first page
  • size: 5 – size of a page
  • salary: 4000 – filtering by salary field
  • agesorting: true – sorting by age
  • desc: true – descending or ascending sorting

– Result:

Nodejs MongoDB Pagination Filtering Sorting RestAPIs request
Nodejs MongoDB Pagination Filtering Sorting RestAPIs request

– Angular Frontend Pagination with Filtering and Sorting table:

Angular 10 Nodejs MongoDB Pagination
Angular 10 Nodejs MongoDB Pagination

Server Side Pagination in Node.js MongoDB RestAPIs

Overview Nodejs MongoDB Architecture with Fullstack Diagram

To handling Nodejs Pagination RestAPI and do Filtering and Sorting queries with MongoDB (Atlas) database, we create a web backend Node.js application with 4 main points:

Nodejs Express MongoDB CRUD RestApis
Nodejs Express MongoDB CRUD RestApis
  • To handle RestAPI requests with Node.js, we use Express.js framework.
  • To do pagination operations with MongoDB database, we use Mongoose ODM queries.
  • We define all Nodejs RestAPI urls in router.js.
  • We implement how to process each RestAPI requests in controller.js file.

How to buid Node.js MongoDB Express Pagination

Create Nodejs Project

Create a folder and named it as Nodejs-Express-MongoDB-Pagination-Example, go inside the folder, open a cmd and initiate a Nodejs project by cmd npm init. After all, a package.json file is created as below content:


{
  "name": "nodejs-mongodb-pagination",
  "version": "1.0.0",
  "description": "server side pagination in nodejs mongodb",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/loizenai"
  },
  "keywords": [
    "nodejs",
    "mongodb",
    "pagination"
  ],
  "author": "https://loizenai.com",
  "license": "ISC",
  "dependencies": {
    "body-parse": "^0.1.0",
    "express": "^4.17.1",
    "mongoose": "^5.10.3"
    "cors": "^5.10.3"
  }
}

Here is the project structure of Nodejs MongoDB Pagination Example:

Nodejs Project structure
Nodejs Project structure

mongodb.config.js is used to define MongoDB database’s URL configuration
customer.model.js is used to define a Mongoose model mapped with corresponding MongoDB database document schema.
router.js is used to define all Nodejs Express RestAPI urls.
controller.js is used to implement detail logic code to process each incoming request.
server.js is used to implement a Nodejs Web server.

To development a ‘Node.js MongoDB Pagination Example with Mongoose and Express RestAPIs’, we need a set of packages to handle the full stack of the web backend proccessing, they includes Express framework, Cors, Body Parse, Mongoose packages.

  1. Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
    $ npm install express
    
  2. CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options.

    
    $npm install cors
    
  3. Body-parser is the Node.js body parsing middleware. It is responsible for parsing incoming request bodies in a middleware before your handlers, available under the req.body property.
    $ npm install body-parser
    
  4. Mongoose is a promise-based Node.js ODM for MongoDB.
    $ npm install --save mongoose

We can install all the packages by one cmd:

$npm install --save express cors body-parser mongoose

Define Nodejs Mongoose Model

In the tutorial, ‘Node.js Express MongoDB Pagination Example’, We need define a Mongoose ODM model to represent a document in the customer collection, see details of coding in file customer.model.js:

const mongoose = require('mongoose');
 
const CustomerSchema = mongoose.Schema({
    firstname: String,
    lastname: String,
    address: String,
    salary: { 
      type: Number, 
      required: true 
    },
    age: { 
      type: Number, 
      min: 18, 
      max: 65, 
      required: true 
    },
    copyrightby: {
      type: String,
      default: 'https://loizenai.com'
    }
});

CustomerSchema.virtual('id').get(function(){
  return this._id.toHexString();
});

// Ensure virtual fields are serialised.
CustomerSchema.set('toJSON', {
  virtuals: true
});

module.exports = mongoose.model('Customer', CustomerSchema);

See a mapping MongoDB document:

MongoDB document sample
MongoDB document sample

Create Express Application Server with MongoDB Connection

To implement an Express RestAPIs Application, firstly we need create a server with express app:

server.js:

const express = require('express');
const app = express();
...
const server = app.listen(8080, function () {
 
  let host = server.address().address
  let port = server.address().port
 
  console.log("App listening at http://%s:%s", host, port); 
})

– For parsing body of requests, we need use body-parser dependency, add more code on server.js file:


...
var bodyParser = require('body-parser');
...
app.use(bodyParser.json());
...
const server = app.listen(8080, function () {
...
>

– To connect with MongoDB, we use below segment code:

mongoose.connect(dbConfig.url, { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => {
        console.log("Successfully connected to MongoDB.");    
    }).catch(err => {
        console.log('Could not connect to MongoDB.');
        process.exit();
    });

To enable CORS function, we need attach it with Express app:

const cors = require('cors')
const corsOptions = {
  origin: 'http://localhost:4200',
  optionsSuccessStatus: 200
}
app.use(cors(corsOptions));

Here is all coding in the server.js file:

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.json())

const cors = require('cors')
const corsOptions = {
  origin: 'http://localhost:4200',
  optionsSuccessStatus: 200
}
app.use(cors(corsOptions));

// Configuring the database
const dbConfig = require('./app/config/mongodb.config.js');
const mongoose = require('mongoose');

mongoose.Promise = global.Promise;
 
// Connecting to the database
mongoose.connect(dbConfig.url, { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => {
        console.log("Successfully connected to MongoDB.");    
    }).catch(err => {
        console.log('Could not connect to MongoDB.');
        process.exit();
    });

require('./app/routes/customer.router.js')(app);
// Create a Server
var server = app.listen(8080, function () { 
  var host = server.address().address
  var port = server.address().port
 
  console.log("App listening at http://%s:%s", host, port) 
})

Define All RestAPI URLs in router.js

For making a routing with Express framework, we define all URLs in customer.router.js file:


module.exports = function(app) {

  var customers = require('../controllers/customer.controller.js');

  app.post('/api/customers/create', customers.create);
  app.get('/api/customers/salaries', customers.getSalaries);
  app.get('/api/customers/pagefiltersort', customers.paginationfilterandsort);}

Configure MongoDB Database with Mongoose ODM

We create a file mongodb.config.js as below:

module.exports = {
  url: 'mongodb+srv://loizenai:loizenai@cluster0.esvi3.mongodb.net/loizenaidb'
}

Implement All RESTAPIs in controllers.js file

Nodejs MongoDB RestAPIs
Nodejs MongoDB RestAPIs

For handling Nodejs MongoDB CRUD RestAPIs’ processing that been defined in router.js file, we implement all working functions in controller.js file:

  1. paginationfilterandsort = async (req, res) function is used to handle a GET request at the endpoint /api/customers/pagefiltersort to do an association quering operation: pagination, plus filtering by salary and sorting by age from MongoDB database with 5 request query parameters limit, page, salary, ageandsorting, desc
  2. getSalaries = (req, res) functions is used to handle a GET request at the endpoint /api/customers/salaries to get a list of distinct salaries from MongoDB documents

Implement Nodejs Mongoose Pagination RestAPIs

paginationfilterandsort = (req, res) function is used to handle a GET request at the endpoint /api/customers/pagefiltersort to do an association quering operation: pagination, plus filtering by salary and sorting by age from database.

To do the pagination with MongoDB database by Mongoose ODM, we use the .find({}) method with skip and limit.

  • Retrieve 5 parameters from incoming request query: limit, page for pagination and salary for filtering and agesorting & desc for sorting
  • Calulate an offset for Sequelize pagining query later:
    const offset = page ? page * limit : 0;
  • Do the Mongoose pagination filtering and sorting with .find({...}) method:
    Straightforward steps to implement the Pagination + Filtering and Sorting function:

    result = await Customer.find({salary: salary})  // You may want to add a query
                                .skip(offset) // Always apply 'skip' before 'limit'
                                .limit(limit)
                                .sort({"age": 1})
                                .select("-__v"); // This is your 'page size'
  • Construct a Json message with informative fields and return back to client side:
    const response = {
      "copyrightby": "https://loizenai.com",
      "totalPages": totalPages,
      "pageNumber": page,
      "pageSize": result.rows.length,
      "customers": result.rows
    };
    res.send(response);
  • Do NOT forget to handle an un-expected errors with try-catch statement.
    try{
        //...
    }catch(error) {
        res.status(500).send({
          message: "Error -> Can NOT complete a paging request!",
          error: error.message,
        });
    }   

– Coding here:

exports.paginationfilterandsort = async (req, res) => {
  try {
    let page = parseInt(req.query.page);
    let limit = parseInt(req.query.size);
    let agesorting = (req.query.agesorting === 'true');
    let desc = (req.query.desc === 'true');
    let salary = req.query.salary ? parseInt(req.query.salary) : -1;
  
    const offset = page ? page * limit : 0;

    console.log("offset = " + offset);    

    let result = {};
    let numOfCustomer;
    
    // NOT Filtering with salary
    if(salary < 0){
      numOfCustomer = await Customer.countDocuments({});
      // not sorting with age
      if(agesorting == false) {
        result = await Customer.find({})  // You may want to add a query
                        .skip(offset) // Always apply 'skip' before 'limit'
                        .limit(limit)
                        .select("-__v"); // This is your 'page size'
      } else {
        if(desc == false) { // sorting with age and ascending
          result = await Customer.find({})  // You may want to add a query
                            .skip(offset) // Always apply 'skip' before 'limit'
                            .limit(limit)
                            .sort({"age": 1})
                            .select("-__v"); // This is your 'page size'
        } else { // sorting with age and descending
            result = await Customer.find({})  // You may want to add a query
                              .skip(offset) // Always apply 'skip' before 'limit'
                              .limit(limit)
                              .sort({"age": -1})
                              .select("-__v"); // This is your 'page size'
        }
      }
    } else { // Filtering with salary

      numOfCustomer = await Customer.countDocuments({salary: salary});
      // not sorting with age
      if(agesorting == false) {
        if(desc == false) { // sorting with age and ascending
          result = await Customer.find({salary: salary})  // You may want to add a query
                            .skip(offset) // Always apply 'skip' before 'limit'
                            .limit(limit)
                            .select("-__v"); // This is your 'page size'
        }
      } else {
        if(desc == false) { // sorting with age and ascending
          result = await Customer.find({salary: salary})  // You may want to add a query
                            .skip(offset) // Always apply 'skip' before 'limit'
                            .limit(limit)
                            .sort({"age": 1})
                            .select("-__v"); // This is your 'page size'
        } else { // sorting with age and descending
          result = await Customer.find({salary: salary})  // You may want to add a query
                            .skip(offset) // Always apply 'skip' before 'limit'
                            .limit(limit)
                            .sort({"age": -1})
                            .select("-__v"); // This is your 'page size'
        }
      }
    }

    const response = {
      "copyrightby": "https://loizenai.com",
      "totalItems": numOfCustomer,
      "totalPages": Math.ceil(numOfCustomer / limit),
      "pageNumber": page,
      "pageSize": result.length,
      "customers": result
    };

    res.status(200).json(response);
  } catch (error) {
    res.status(500).send({
      message: "Error -> Can NOT complete a paging request!",
      error: error.message,
    });
  }
};

Implement Nodejs GET all Distinct Salary RestAPIs

getSalaries = (req, res) function is used to handle a GET request at the endpoint /api/customers/getSalaries and returns a list of distinct salaries from database:

exports.getSalaries = (req, res) => {
  try {
    Customer.find({}).distinct("salary")
                .then(result => {
                  let salaries = result.sort(function(a, b){return b - a});
                  res.send(salaries);
                });
  } catch (error) {
    res.status(500).send({
      message: "Error -> Can NOT get all customer's salaries",
      error: error.message
    });
  }
}
Nodejs Get Distinct salaries from MongoDB
Nodejs Get Distinct salaries from MongoDB

Angular 10 Pagination Table – Frontend Development

Related post:

Server Side Pagination in Nodejs MongoDB (Atlas) RestAPIs Example

Angular 10 Pagination Table Overview

Angular Pagination Application Architecture Design
Angular Pagination Application Architecture Design

Create Angular 10 Project

We create an Angular Pagination Application with 2 main blocks:

  1. CustomerService is used to fetch data through Angular HTTP Client
  2. TableComponent is used to display data with pagination, filtering and sorting view
Angular 10 Nodejs MongoDB Paginatin Filtering with salary = 4000
Angular 10 Nodejs MongoDB Paginatin Filtering with salary = 4000

Here is the Angular project structure:

Angular 10 Table Pagination with Nodejs RestAPIs Project Structure
Angular 10 Table Pagination with Nodejs RestAPIs Project Structure

– We implement a component: angulartable with 2 files angulartable.component.html and angular.component.ts to display data on web.
– We implement a service customer.service.ts using Httpclient to interact with SpringBoot restPAPI to fetch data with pagination, filtering and sorting.
– We define 2 helpful class customer.ts and message.ts for mapping data with RestAPIs.

Define Model Customer class

export class Customer {
    id: number;
    firstname: string;
    lastname: string;
    age: number;
    salary: number;
    address: string;
    copyrightBy: string;
}

Define Message.ts class

import { Customer } from './customer';

export class Message {
    customers: Customer[];
    totalPages: number;
    pageNumber: number;
    pageSize: number;
}

Implement Angular 10 Customer Service

We implement CustomerService service which uses a built-in HttpClient of Angular to create 2 http methods:

  • getPagableCustomers() is used to a Page of Customer from SpringBoot RestAPI with 5 params for pagination, filtering and sorting data
  • getListSalaries() is used to get a list distinct salaries of Customer

Coding:

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';

import { Observable, throwError  } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';

import { Message } from './message';

@Injectable({
  providedIn: 'root'
})
export class CustomerService {

  private baseUrl = 'http://localhost:8080/api/customers';

  constructor(private http: HttpClient) { }


  /**
   * Retrieve all customer from Backend
   */
  getPagableCustomers(pageNumber: number, 
                        pageSize: number, salary: number,
                        agesorting: boolean, desc: boolean): Observable<Message> {
    // Initialize Params Object
    let params = new HttpParams();

    // Begin assigning parameters
    params = params.append('page', pageNumber.toString());
    params = params.append('size', pageSize.toString());
    params = params.append('salary', salary.toString());
    params = params.append('agesorting', agesorting.toString());
    params = params.append('desc', desc.toString());

    return this.http.get<Message>(`${this.baseUrl}` + `/custom/pageable`, { params: params })
                  .pipe(
                    retry(3),
                    catchError(this.handleError)
                  );
  }

  getListSalaries(): Observable<Array<number>>{
    return this.http.get<Array<number>>(`${this.baseUrl}` + `/salaries`)
                  .pipe(
                    retry(3),
                    catchError(this.handleError)
                  );
  }

  private handleError(error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error('An error occurred:', error.error.message);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong,
      console.error(
        `Backend returned code ${error.status}, ` +
        `body was: ${error.error}`);
    }
    // return an observable with a user-facing error message
    return throwError(
      'Something bad happened; please try again later.');
  };
}

Implement Angular 10 Table Component

Implement Angular View Page

We use Bootstrap to implement the view page, so we need add it to the head tag of index.html page.
– Coding of index.html


<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>AngularHttpclient</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
  <!-- jQuery library -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <!-- Popper JS -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
  <!-- Latest compiled JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
</head>
<body>
  <div class="container" > 
    <div class="col-sm-7" style="background-color: #e6f9ff; margin:10px;padding:10px; border-radius: 5px">
      <div class="alert alert-danger">
          <h3>Angular + SpringBoot Tutorial</h3>
          <h6><strong>Pagination + Filtering and Sorting </strong></h6>
          <hr>
          <p>@Copyright by <strong><span style="color:blue"><a href="https://loizenai.com">https://loizenai.com</a></span></strong><br>
              youtube: <strong><a href="https://www.youtube.com/channel/UChkCKglndLes1hkKBDmwPWA"><span style="color:crimson">loizenai</span></a></strong>
          </p>
      </div>
      <app-root></app-root>
    </div> 
  </div>
</body>
</html>

In the tutorial “Angular 10 Nodejs Pagination RestAPIs Example”, we develop table component to do the task with 3 main parts for Angular view page to display data with pagination, filtering and sorting:

  • a Bootstrap table with a pagination bar to display data and selecting a specific pagination
  • a Select List for distinct salaries to choose a specific salary for doing the pagination with salary filtering
  • a checkbox group with 2 fields age and desc to make a decission to sort or NOT with the age field by descending or ascending direction

– Code of angulartable.component.html view page:

<div *ngIf="customers.length">
    <h3>Processing...</h3>
    <br>
    <div>    
      <h5>Do you want sorting by?</h5>
      <form>
        <label class="checkbox-inline">
          <input type="checkbox"
                  [checked]="agesorting"
                  (change)="onAgeSortingChange($event)"> Age
          <br>
          <input  [disabled]="!agesorting" type="checkbox"
                  [checked]="desc"
                  (change)="desc = !desc"> Desc
        </label>
      </form>
      <button type="button" class="btn btn-primary" (click)="sortNow()">Sort Now</button>
    </div>
    <hr>
    <div class="form-group">
      <label for="sel1"><h5>Filtering by Salary:</h5></label>
      <select class="form-control" id="salary_filtering"
                    (change)="getCustomerPagesWithSalaryFiltering($event.target.value)">
        <option>All</option>
        <option *ngFor="let salary of salaries">{{salary}}</option>
      </select>
    </div>
    <h3>Customers</h3>
    <table class="table table-hover table-sm">
        <thead class="thead-dark">
          <tr>
            <th>Id</th>
            <th>Firstname</th>
            <th>Lastname</th>
            <th>Age</th>
            <th>Salary</th>
            <th>Address</th>
            <th>Copyright By</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
            <tr *ngFor="let customer of customers">
              <td>{{customer.id}}</td>
              <td>{{customer.firstname}}</td>
              <td>{{customer.lastname}}</td>
              <td>{{customer.age}}</td>
              <td>{{customer.salary}}</td>
              <td>{{customer.address}}</td>
              <td><a href="https://loizenai.com">{{customer.copyrightBy}}</a></td>
            </tr>
        </tbody>
    </table>
</div>

<ul class="pagination justify-content-center">
  <li class="page-item"><a (click)="previousClick()" class="page-link">Previous</a></li>
  <li *ngFor='let index of pageIndexes' class="page-item" [ngClass]="active(index)">
          <a (click)="getPaginationWithIndex(index)" class="page-link">
            {{index + 1}}
          </a>
  </li>
  <li class="page-item"><a (click)="nextClick()" class="page-link">Next</a></li>
</ul>

Implement Angular Table Component

We develop Table Component angulartable.component.ts arround below list of variables:

  currentSelectedPage:number = 0;
  totalPages: number = 0;
  customers: Array<Customer> = [];
  pageIndexes: Array<number> = [];

  // salaries list
  salaries: Array<number> = [];
  selectedSalary: number = -1;

  // sorting
  agesorting: boolean = false;
  desc: boolean = false;

What does it mean?

  • currentSelectedPage defines the current selected page for getting data
  • totalPages is the number page of data for pagination
  • customers contains a list customers of current fetching page
  • pageIndexes contains an array numbers that presents the number of pages to show for user selecting
  • salaries contains a list of salaries with distinct values
  • selectedSalary is a selected salary for filtering with pagination
  • ageSorting is a boolean variable that determines whether the sorting with age field or not
  • desc is a boolean variable that is used to determines a direction for sorting by descending or ascending (the default value)

We use the CustomerService to fetch data from Nodejs Pagination RestAPIs, so we need to inject it in the constructor:


  constructor(private customerService: CustomerService) { 
  }
Angular Display the Fetching Data with Table and Pagination Bar

For getting data from SpringBoot RestAPI, we define a function getPage() as below:

  getPage(page: number, selectedSalary: number, agesorting: boolean, desc: boolean){

    this.customerService.getPagableCustomers(page, pageSize, selectedSalary, 
                                              agesorting, desc)
            .subscribe(
                (message: Message) => {
                  console.log(message);
                  this.customers = message.customers;
                  this.totalPages = message.totalPages;
                  this.pageIndexes = Array(this.totalPages).fill(0).map((x,i)=>i);
                  this.currentSelectedPage = message.pageNumber;
                },
                (error) => {
                  console.log(error);
                }
            );
  }

At the initial time of Angular loading page, we need invoke the getPage function with appropriate parameters as below code:

  ngOnInit(): void {
    // get the first Page
    this.getPage(0, -1, false, false);
    // get Salaries
    this.getSalaries();
  }

On the view page, for showing the fetching data, we need binding them as below html code using the Angular directive:

<tbody>
    <tr *ngFor="let customer of customers">
      <td>{{customer.id}}</td>
      <td>{{customer.firstname}}</td>
      <td>{{customer.lastname}}</td>
      <td>{{customer.age}}</td>
      <td>{{customer.salary}}</td>
      <td>{{customer.address}}</td>
      <td><a href="https://loizenai.com">{{customer.copyrightBy}}</a></td>
    </tr>
</tbody>

Here is the how we build the pagination bar on Html view by binding the fetching data pageIndexes with html element by using a Angular built-in directive *ngFor:

<ul class="pagination justify-content-center">
  <li class="page-item"><a (click)="previousClick()" class="page-link">Previous</a></li>
  <li *ngFor='let index of pageIndexes' class="page-item" [ngClass]="active(index)">
          <a (click)="getPaginationWithIndex(index)" class="page-link">
            {{index + 1}}
          </a>
  </li>
  <li class="page-item"><a (click)="nextClick()" class="page-link">Next</a></li>
</ul>

For processing the navigation with Next and Previous buttons on Pagination bar, we define 2 funtions nextClick() and previousClick() as below:


 nextClick(){
    if(this.currentSelectedPage < this.totalPages-1){
      this.getPage(++this.currentSelectedPage,
                   this.selectedSalary, this.agesorting, this.desc);
    }  
  }

  previousClick(){
    if(this.currentSelectedPage > 0){
      this.getPage(--this.currentSelectedPage,
                   this.selectedSalary, this.agesorting, this.desc);
    }  
  }
}
Angular Do the Pagination Filtering with salary selected option

Review again the implementation in html view page for the filtering select-option form funtion:

<div class="form-group">
  <label for="sel1"><h5>Filtering by Salary:</h5></label>
  <select class="form-control" id="salary_filtering"
                (change)="getCustomerPagesWithSalaryFiltering($event.target.value)">
    <option>All</option>
    <option *ngFor="let salary of salaries">{{salary}}</option>
  </select>
</div>

We use the Angular built-in directive *ngFor to build the selected-option list of distinct value of salaries.

The salaries list is fetched at the initial time of Angular Component:

  ngOnInit(): void {
    ...
    // get Salaries
    this.getSalaries();
  }

  getSalaries() {
    this.customerService.getListSalaries()
          .subscribe(
            (salaries: Array<number>) => {
              console.log(salaries);
              this.salaries = salaries;
            },
            (error) => {
              console.log(error);
            }
          );
  }

If having any change from selected form, the function (change)="getCustomerPagesWithSalaryFiltering($event.target.value) will be invoked:

getCustomerPagesWithSalaryFiltering(optionValue: any) {
  // convert option string value to appropriate number
  if(optionValue != "All"){
    this.selectedSalary = parseInt(optionValue);
  } else {
    this.selectedSalary = -1;
  }

  // load customer again with filtering and pagination api
  this.getPage(0, this.selectedSalary, this.agesorting, this.desc);
}
Anglar Sorting Implementation with age and desc checkbox

Review again the html sorting div:

<div>    
  <h5>Do you want sorting by?</h5>
  <form>
    <label class="checkbox-inline">
      <input type="checkbox"
              [checked]="agesorting"
              (change)="onAgeSortingChange($event)"> Age
      <br>
      <input  [disabled]="!agesorting" type="checkbox"
              [checked]="desc"
              (change)="desc = !desc"> Desc
    </label>
  </form>
  <button type="button" class="btn btn-primary" (click)="sortNow()">Sort Now</button>
</div>

If having any changes on the checkboxes, they will immediately affect to the 2 variables agesorting and desc.


//.html file
(change)="onAgeSortingChange($event)"
(change)="desc = !desc"

//.ts file
onAgeSortingChange(value: any){
    this.agesorting = !this.agesorting;
    if(!this.agesorting){
      // reset desc
      this.desc = false;
    }
  }

When pressing the button Sort Now, the function sortNow() will be invoked for re-fetching the pagination and sorting data from SpringBoot RestAPI:

sortNow(){
  if(this.desc == true && this.agesorting == false){
    alert("Please select 'agesorting' option before selecting 'desc' option!");
    return;
  }
  // load again from backend for sorting with age field
  this.getPage(0, this.selectedSalary, this.agesorting, this.desc);
}
angular.component.ts code
import { Component, OnInit, ɵclearResolutionOfComponentResourcesQueue } from '@angular/core';
import { CustomerService } from '../customer.service';
import { Message } from '../message';
import { Customer } from '../customer';

const pageSize:number = 5;

@Component({
  selector: 'app-angulartable',
  templateUrl: './angulartable.component.html'
})
export class AngulartableComponent implements OnInit {
  currentSelectedPage:number = 0;
  totalPages: number = 0;
  customers: Array<Customer> = [];
  pageIndexes: Array<number> = [];

  // salaries list
  salaries: Array<number> = [];
  selectedSalary: number = -1;

  // sorting
  agesorting: boolean = false;
  desc: boolean = false;

  constructor(private customerService: CustomerService) { 
  }

  ngOnInit(): void {
    // get the first Page
    this.getPage(0, -1, false, false);
    // get Salaries
    this.getSalaries();
  }

  getPage(page: number, selectedSalary: number, agesorting: boolean, desc: boolean){

    this.customerService.getPagableCustomers(page, pageSize, selectedSalary, 
                                              agesorting, desc)
            .subscribe(
                (message: Message) => {
                  console.log(message);
                  this.customers = message.customers;
                  this.totalPages = message.totalPages;
                  this.pageIndexes = Array(this.totalPages).fill(0).map((x,i)=>i);
                  this.currentSelectedPage = message.pageNumber;
                },
                (error) => {
                  console.log(error);
                }
            );
  }

  getPaginationWithIndex(index: number) {
    this.getPage(index, this.selectedSalary, this.agesorting, this.desc);
  }

  getSalaries() {
    this.customerService.getListSalaries()
          .subscribe(
            (salaries: Array<number>) => {
              console.log(salaries);
              this.salaries = salaries;
            },
            (error) => {
              console.log(error);
            }
          );
  }

  getCustomerPagesWithSalaryFiltering(optionValue: any) {
    // convert option string value to appropriate number
    if(optionValue != "All"){
      this.selectedSalary = parseInt(optionValue);
    } else {
      this.selectedSalary = -1;
    }

    // load customer again with filtering and pagination api
    this.getPage(0, this.selectedSalary, this.agesorting, this.desc);
  }

  sortNow(){
    if(this.desc == true && this.agesorting == false){
      alert("Please select 'agesorting' option before selecting 'desc' option!");
      return;
    }
    // load again from backend for sorting with age field
    this.getPage(0, this.selectedSalary, this.agesorting, this.desc);
  }

  onAgeSortingChange(value: any){
    this.agesorting = !this.agesorting;
    if(!this.agesorting){
      // reset desc
      this.desc = false;
    }
  }

  active(index: number) {
    if(this.currentSelectedPage == index ){
      return {
        active: true
      };
    }
  }

  nextClick(){
    if(this.currentSelectedPage < this.totalPages-1){
      this.getPage(++this.currentSelectedPage,
                   this.selectedSalary, this.agesorting, this.desc);
    }  
  }

  previousClick(){
    if(this.currentSelectedPage > 0){
      this.getPage(--this.currentSelectedPage,
                   this.selectedSalary, this.agesorting, this.desc);
    }  
  }
}

Integrative Testing – Angular Nodejs MongoDB Pagination

Below is a suite of testcases for the tutorial: “Angular 10 Nodejs MongoDB Pagination RestAPIs Example (Server Side Pagination)”.

– MongoDB’s documents:

MongoDB documents
MongoDB documents

Testcase 1 – Angular 10 Table Pagination

– Start time:

Integrative testing - testcase 1 - Angular 10 Table Pagination at start time
Integrative testing – testcase 1 – Angular 10 Table Pagination at start time

– Select Page 3:

Integrative testing - testcase 2 - Angular 10 Table Pagination select page 3
Integrative testing – testcase 2 – Angular 10 Table Pagination select page 3

Testcase 2 – Angular 10 Pagination and Filtering

– Pagination and Filtering with salary is $4000:

Integrative Testing - Testcase 2 - Angular Nodejs MongoDB Pagination - Select Salary = $4000
Integrative Testing – Testcase 2 – Angular Nodejs MongoDB Pagination – Select Salary = $4000

– Pagination and Filtering with Salary = $3500:

Integrative testing - testcase 2 - Angular Nodejs MongoDB Pagination - Select salary $3500
Integrative testing – testcase 2 – Angular Nodejs MongoDB Pagination – Select salary $3500

Testcase 3 – Angular 10 Pagination Filtering and Sorting

– Pagination and Filtering with salary is $3000 and Sorting by Age:

Integrative testing - testcase 3 - Angular Nodejs MongoDB - Pagination Filtering salary=3000 and sorting with ascending order
Integrative testing – testcase 3 – Angular Nodejs MongoDB – Pagination Filtering salary=3000 and sorting with ascending order

– Pagination and Filtering with salary = $3500 and sorting by Age with Desc direction:

Integrative testing - testcase 3 - Angular Nodejs MongoDB - Pagination Filtering with Salary=$3500 and Sorting by age with descending order
Integrative testing – testcase 3 – Angular Nodejs MongoDB – Pagination Filtering with Salary=$3500 and Sorting by age with descending order

Sourcecode

The sourcecode for a tutorial: “Angular 10 Server Side Pagination in Nodejs MongoDB Example”:

– Nodejs MongoDB Pagination Example:

Nodejs-MongoDB-Pagination

– Angular Pagination Example:

AngularTablePagination

Github Sourcecode:

Nodejs MongoDB Pagination Example

Angular 10 Pagination Table

Further Reading

Should Reading:


Leave a Reply

Your email address will not be published. Required fields are marked *