[Angular SpringBoot] Angular Table Pagination Filtering Sorting with SpringBoot RestAPIs + Bootstrap Example – Angular 10-9-8-6 Tutorial

Angular 10 Table Pagination Filtering Sorting integrate with SpringBoot RestAPIs Examples Tutorial

When we have a large dataset and we want to present it to the user in smaller chunks, pagination and sorting is often helpful solution. So in the tutorial, I introduce how to use Angular to build a table solution for pagination, filtering and sorting the fetching data with SpringBoot RestAPIs examples.

Related posts:


Overview Angular Springboot Table Pagination Filtering Sorting

Architecture Design

Fullstack Overview Architecture - Angular Table Pagination Filtering Sorting integrate with Springboot RestAPIs
Fullstack Overview Architecture – Angular Table Pagination Filtering Sorting integrate with Springboot RestAPIs

In the tutorial, We develop 2 projects:

  • Backend Project – SpringBoot Application gets data from MySQL/PostgreSQL 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

Goal

– Make a request at API: /api/customers/custom/pageable 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:

SpringBoot RestAPIs Pagination Filtering and Sorting request
SpringBoot RestAPIs Pagination Filtering and Sorting request

– Angular Frontend Pagination with Filtering and Sorting table:

Angular Pagination Filtering Sorting - View Page
Angular Pagination Filtering Sorting – View Page

SpringBoot RestAPIs Pagination – Backend Development

SpringBoot Pagination Filtering and Sorting RestAPIs

For doing the pagination, we use PagingAndSortingRepository class that is an extension of CrudRepository to provide additional methods to retrieve entities using the pagination and sorting abstraction. Here is the sourcecode of it:


@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
	Iterable<T> findAll(Sort sort);
	Page<T> findAll(Pageable pageable);
}
  • findAll(Sort sort) returns all entities sorted by the given options.
  • findAll(Pageable pageable) Returns a page of entities meeting the paging restriction provided in the Pageable object.

Here is the hierarchy of PagingAndSortingRepository :

PagingAndSortingRepository hierarchy
PagingAndSortingRepository hierarchy

So JpaRepository of Spring JPA is an alternative solution for PagingAndSortingRepository:


@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

Spring JPA Pagination

For pagination with Spring JPA, we use the methods:
Page<T> findAll(Pageable pageable);

It returns a Page of entities meeting the paging restriction provided in the Pageable object.

– Here is how the Page interface is defined:

Page Interface
Page Interface

– Here is how to Pageable interface is defined:

Pagable Interface for pagination, filtering and sorting
Pagable Interface for pagination, filtering and sorting

Examples coding:


Pageable requestedPage = PageRequest.of(0, 5);
Page<Customer> customers  = customerRepository.findAll(requestedPage);

We use the PageRequest to construct a Pageable object then pass it to the findAll() method of PagingAndSortingRepository.

How to Pagining and Filtering?

For filtering data with pagination, Spring JPA provides many useful Query Creation from method names:


Slice findAllBySalary (double salary, Pageable pageable);
Page findAllByAgeGreaterThan(int age, Pageable pageable);
  • GreaterThan examples findAllByAgeGreaterThan(int age, Pageable pageable); means … where x.age> ?1
  • Is, Equals examples findAllBySalary (double salary, Pageable pageable) means … where x.salary = ?1

Paging and Sorting

For sorting with Spring data JPA, we use the function: Iterable<T> findAll(Sort sort);.

We can use public static methods by() to build the Sort objects:

  • public static Sort by(String... properties) creates a new Sort for the given properties.
  • public static Sort by(List≶Order> orders) creates a new Sort for the given list Orders
  • public static Sort by(Order... orders) create a new Sort for the given Orders
  • public static Sort by(Direction direction, String... properties)

If we want to both sort and page our data, We can do that by passing the sorting details into our PageRequest object itself:

Pageable sortedBySalary = 
  PageRequest.of(0, 3, Sort.by("salary"));
 
Pageable sortedBySalaryDesc = 
  PageRequest.of(0, 3, Sort.by("salary").descending());
 
Pageable sortedBySalaryDescAgeAndFirstnameAsc = 
  PageRequest.of(0, 5, Sort.by("salary").descending().and(Sort.by("age")).and(Sort.by("firstname")));

For more details about how to builld a SpringBoot RestAPIs for pagination, filtering and sorting, please follow the link at here.

How to build Spring Boot Pagination Project

Create SpringBoot project

We create a SpringBoot project with 3 dependencies:

  • Spring Web is used to handle RestAPIs
  • Spring JPA is used to do pagination, filtering and sorting with database’s data
  • MySQL/PostgreSQL driver is used to work with dabase

Check the pom.xml file:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>		

Configure Database

Open the application.properties to configure the working database environment:

spring.datasource.url=jdbc:mysql://localhost:3306/loizenjavadb
spring.datasource.username=root
spring.datasource.password=12345
spring.jpa.generate-ddl=true

#drop & create table again, good for testing, comment this in production
spring.jpa.hibernate.ddl-auto=create

Create JPA Data Model

We create a Customer model class with 7 attributes: id, firstname, lastname, address, age, salary, copyrightBy.

Customer Model class
Customer Model class
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="customer")
public class Customer {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;
	
	@Column
	private String firstname;
	
	@Column
	private String lastname;

	@Column
	private String address;
	
	@Column
	private int age;
	
	@Column
	private double salary;
	
	@Column(columnDefinition = "varchar(255) default '@ https://loizenjava.com'")
	private String copyrightBy;

@Column specifies the mapped column for a persistent property or field. If no Column annotation is specified, the default values apply.
javax.persistence.Id specifies the primary key of an entity.
javax.persistence.Entity specifies that the class is an entity. This annotation is applied to the entity class.

Define JPA Paging and Sorting Repository

Create interface CustomerRepository extends PagingAndSortingRepository:

package com.loizenjava.springboot.pagingansorting.repository;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;

import com.loizenjava.springboot.pagingansorting.model.Customer;

@Repository
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long>{	
	Page<Customer> findAllBySalary (double salary, Pageable pageable);
	
	@Query("SELECT DISTINCT c.salary FROM Customer c")
	List<Double> findDistinctSalary();
}

Implement SpringBoot Pagination Filtering and Sorting RestAPI

We implement a SpringBoot RestAPI at URL /api/customers/custom/pageable to do pagination, filtering and sorting Customer with 5 params:

  • page is used to get the right page
  • size is a size of a page
  • salary is used to filtering
  • agesorting is used to sort with age column or not
  • desc is used to define the sorting direction – descending or default with ascending sorting
  • Coding:

    package com.loizenjava.springboot.pagingansorting.restapis;
    
    import java.util.Arrays;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.domain.Sort;
    import org.springframework.data.repository.query.Param;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import com.loizenjava.springboot.pagingansorting.model.Customer;
    import com.loizenjava.springboot.pagingansorting.model.Response;
    import com.loizenjava.springboot.pagingansorting.repository.CustomerRepository;
    
    @org.springframework.web.bind.annotation.RestController
    @RequestMapping("/api/customers")
    @CrossOrigin(origins = "http://localhost:4200")
    public class RestController {
    		
    	@Autowired
    	CustomerRepository customerRepository;
    		
    	@GetMapping("/custom/pageable")
    	public Response retrieveCustomer(@Param(value = "salary") int salary,
    										@Param(value = "page") int page, 
    										@Param(value = "size") int size,
    										@Param(value = "agesorting") boolean agesorting,
    										@Param(value = "desc") boolean desc){
    		
    		Page<Customer> customers = null;
    		
    		// not filtering with salary
    		if(salary < 0) {
    			// not sorting with age
    			if(agesorting == false) {
    				Pageable requestedPage = PageRequest.of(page, size);
    				customers = customerRepository.findAll(requestedPage);	
    			}else {
    				// sorting with age and ascending
    				if(false == desc) {
    					Pageable requestedPage = PageRequest.of(page, size, Sort.by("age"));
    					customers  = customerRepository.findAll(requestedPage);
    				}
    				// sorting with age and descending
    				else {
    					Pageable requestedPage = PageRequest.of(page, size, 
    												Sort.by("age").descending());
    					customers  = customerRepository.findAll(requestedPage);
    				}
    			}
    		// Filtering with salary
    		} else {			
    			// not sorting with age
    			if(agesorting == false) {
    				Pageable requestedPage = PageRequest.of(page, size);
    				// fitering request
    				customers = customerRepository.findAllBySalary(salary, requestedPage);	
    			}else {
    				// sorting with age and ascending
    				if(false == desc) {
    					Pageable requestedPage = PageRequest.of(page, size, Sort.by("age"));
    					// filtering request
    					customers  = customerRepository.findAllBySalary(salary, requestedPage);
    				}
    				// sorting with age and descending
    				else {
    					Pageable requestedPage = PageRequest.of(page, size, 
    												Sort.by("age").descending());
    					// filtering request
    					customers  = customerRepository.findAllBySalary(salary, requestedPage);
    				}
    			}
    		}
    		
    		Response res = new Response(customers.getContent(), customers.getTotalPages(),
    										customers.getNumber(), customers.getSize());
    		
    		return res;
    	}
    		
    	@GetMapping("/salaries")
    	public List<Double> getListSalaries() {
    		try {
    			return customerRepository.findDistinctSalary();
    		}catch(Exception e) {
    			// Log errors to user monitoring
    			System.out.println(e);
    			return Arrays.asList();
    		}
    	}
    }

    Implement SpringBoot Main Class

    In the main class of SpringBoot project, We implement the CommandLineRunner interface and inject the CustomerRepository repository to save a list Customer to database MySQL/PostgreSQL.

    Coding:

    package com.loizenjava.springboot.pagingansorting;
    
    import java.util.Arrays;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    import com.loizenjava.springboot.pagingansorting.model.Customer;
    import com.loizenjava.springboot.pagingansorting.repository.CustomerRepository;
    
    @SpringBootApplication
    public class SpringBootPagingAndSortingApplication implements CommandLineRunner{
    
    	@Autowired
    	CustomerRepository customerRepository;
    	
    	public static void main(String[] args) {
    		SpringApplication.run(SpringBootPagingAndSortingApplication.class, args);
    	}
    
    	@Override
    	public void run(String... args) throws Exception {
    		List<Customer> customers = Arrays.asList(	new Customer("Jack", "Smith", "Massachusetts", 23, 4000)
    						, new Customer("Adam", "Johnson", "New York", 27, 3000)
    						, new Customer("Katherin", "Carter", "Washington DC", 26, 4000)
    						, new Customer("Jack", "London", "Nevada", 33, 4000)
    						, new Customer("Jason", "Bourne", "California", 36, 3000)
    						, new Customer("Blade", "Johnson", "Ohio", 18, 3000)
    						, new Customer("Carol", "Carter", "Florida", 23, 4000)
    						, new Customer("Avery", "Alvin", "Washington DC", 41, 3000)
    						, new Customer("Dana", "Bay", "Texas", 32, 4000)
    						, new Customer("Tom", "Bourne", "Colorado", 28, 3500)
    						, new Customer("Ardys", "Bean", "Alaska", 23, 4000)
    						, new Customer("Carol", "Carter", "Arizona", 26, 3500)
    						, new Customer("Avery", "Zane", "Virginia", 31, 4000)
    						, new Customer("Barric", "Weldin", "Oregon", 52, 3000)
    						, new Customer("Galen", "Wayt", "Missouri", 48, 4000)
    						, new Customer("Mayer", "Acomb", "Wisconsin", 32, 4000)
    						, new Customer("Sadie", "Albright", "Tennessee", 23, 3500)
    						, new Customer("Norvell", "Baily", "Oregon", 41, 4000)
    						, new Customer("Tanzi", "Baney", "North Dakota", 26, 4000)
    						, new Customer("Osric", "Callender", "New Mexico", 29, 3000)
    						, new Customer("Trudy", "Zane", "Vermont", 31, 3500)
    						, new Customer("Reynold", "Zone", "Wyoming", 43, 3000)
    						, new Customer("Udele", "Wheetley", "Michigan", 37, 3500)
    						, new Customer("Blackburn", "Atkinson", "Illinois", 19, 4000)
    						, new Customer("Cotovatre", "Bailey", "Delaware", 26, 4000));
    		
    		customerRepository.saveAll(customers);
    	}
    }
    

    Testing SpringBoot Pagination RestAPI

    RestAPI Testcase 1 – Get All Distinct Salaries

    – Make a request to ULR: /api/customers/salaries

    SpringBoot RestAPI - Get a list distinct salaries
    SpringBoot RestAPI – Get a list distinct salaries

    RestAPI Testcase 2 – Pagination Filtering and Sorting RestAPI

    – Make a request at API: /api/customers/custom/pageable 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:

    SpringBoot RestAPIs Pagination Filtering and Sorting request
    SpringBoot RestAPIs Pagination Filtering and Sorting request

    Angular Table Pagination – Frontend Development

    Angular Pagination’s Features Overview

    Angular Application Architecture Design
    Angular Application Architecture Design

    We create an Angular Application with 2 main blocks:

    • CustomerService is used to fetch data through Angular HTTP Client
    • TableComponent is used to display data with pagination, filtering and sorting view
    Angular Pagination Filtering Sorting - View Page
    Angular Pagination Filtering Sorting – View Page

    Here is the Angular project structure:

    Angular Project Structure
    Angular 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.

    Develop Angular Application – How to code it?

    Create Angular Project

    Create an Angular project by commandline: ng new AngularTable
    Create an Angular component by cmd: ng generate c angulartable
    Create an Angular service by cmd: ng gererate s customer
    Create Angular classes customer.ts and message.ts by cmd:
    ng generate class customer
    ng generate class message

    Define Customer.ts 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 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.');
      };
    }

    Develop Table Component

    Implement 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://loizenjava.com">https://loizenjava.com</a></span></strong><br>
                  youtube: <strong><a href="https://www.youtube.com/channel/UChkCKglndLes1hkKBDmwPWA"><span style="color:crimson">loizenjava</span></a></strong>
              </p>
          </div>
          <app-root></app-root>
        </div> 
      </div>
    </body>
    </html>
    

    To display data with pagination, filtering and sorting, we develop table component to do the task with 3 main parts for Angular view page:

    • 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://loizenjava.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 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 SpringBoot RestAPIs, so we need to inject it in the constructor:

    
      constructor(private customerService: CustomerService) { 
      }
    
    Show 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://loizenjava.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);
        }  
      }
    }
    
    Do the Pagination and 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);
    }
    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);
        }  
      }
    }

    Integration Testing – Angular Table & SpringBoot RestAPI

    Testcase 1 – Pagination View

    – Start time:

    Testcase 1 - Integrated Angular SpringBoot RestAPI - Pagination Request
    Testcase 1 – Integrated Angular SpringBoot RestAPI – Pagination Request

    – Select page 3:

    Testcase 1 - Integrated Angular SpringBoot RestAPI - Pagination Request - Select Page 3
    Testcase 1 – Integrated Angular SpringBoot RestAPI – Pagination Request – Select Page 3

    Testcase 2 – Pagination and Filtering View

    – Pagination and Filtering with salary is $4000:

    Testcase 2 - Integrated Angular SpringBoot RestAPI - Pagination and Filtering with salary = $4000
    Testcase 2 – Integrated Angular SpringBoot RestAPI – Pagination and Filtering with salary = $4000

    – Pagination and Filtering with Salary = $3500:

    Testcase 2 - Integrated Angular SpringBoot RestAPI - Pagination and Filtering with salary = $3500
    Testcase 2 – Integrated Angular SpringBoot RestAPI – Pagination and Filtering with salary = $3500

    Testcase 3 – Pagination Filtering and Sorting View

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

    Testcase 3 - Integrated Angular SpringBoot RestAPI - Pagination and Filtering with salary = $3000 and Age sorting
    Testcase 3 – Integrated Angular SpringBoot RestAPI – Pagination and Filtering with salary = $3000 and Age sorting

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

    Testcase 3 - Integrated Angular SpringBoot RestAPI - Pagination and Filtering with salary = $3000 and Age sorting with Desc direction
    Testcase 3 – Integrated Angular SpringBoot RestAPI – Pagination and Filtering with salary = $3000 and Age sorting with Desc direction

    Sourcecode

    – SpringBoot Sourcecode:

    SpringBootPagingAndSorting

    – SpringBoot RestAPI – GitHub Sourcode:

    SpringBoot Pagination RestAPIs – GitHub

    – Angular Sourcecode:

    AngularTablePagination

    – GitHub Source:

    AngularPaginationTable – GitHub

12 thoughts on “[Angular SpringBoot] Angular Table Pagination Filtering Sorting with SpringBoot RestAPIs + Bootstrap Example – Angular 10-9-8-6 Tutorial”

Leave a Reply

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