In the previous article, we have implemented a CRUD application. As part of the Read operation, we need to list all the available products from the database. If there are 100s of records in the database, then it is not advisable to fetch all of them at once and display them in the frontend. If we do so, then the performance of both frontend and backend will be impacted since all the fetched records will be placed in memory. The loading & rendering time also will be high based on the volume of data. Hence, instead of fetching all the data at once, we are gonna implement pagination in which we will fetch records page by page.
What You’ll Build
Angular Frontend
List Page
Spring Boot Backend
Implement the /api/products
REST endpoint to return the paginated data.
What You’ll Need
Run the following checklist before you begin the implementation:
- Spring Tool Suite 4 or any other IDE of your choice
- Lombok
- JDK 11
- Maven
- Spring Boot + Angular Application Source Code
Angular Client Implementation
Install Angular Material
We are gonna use Angular Material to implement the pagination on the frontend. So lets install the angular material dependency with the following command
ng add @angular/material
It will ask you to
- Select a material theme -> I have chosen Deep Purple/Amber theme since it suits well with the existing look & feel of the application
- Set up global Angular Material typography styles -> Enter ‘N’ to skip it since we already have bootstrap style in place
- Set up browser animations for Angular Material -> Enter ‘Y’ to set up browser animation. Importing the
BrowserAnimationsModule
into your application enables Angular’s animation system. Declining this will disable most of Angular Material’s animations.
Output
Skipping installation: Package already installed
? Choose a prebuilt theme name, or "custom" for a custom theme: (Use arrow keys)
> Indigo/Pink [ Preview: https://material.angular.io?theme=indigo-pink ]
? Choose a prebuilt theme name, or "custom" for a custom theme:
Indigo/Pink [ Preview: https://material.angular.io?theme=indigo-pink ]
> Deep Purple/Amber [ Preview: https://material.angular.io?theme=deeppurple-amb
? Choose a prebuilt theme name, or "custom" for a custom theme: Deep Purple/Amber [ Preview: https://material.angular.io?theme=deeppurple-amber ]
? Set up global Angular Material typography styles? No
? Set up browser animations for Angular Material? Yes
UPDATE package.json (1325 bytes)
√ Packages installed successfully.
Two or more projects are using identical roots. Unable to determine project usin
g current working directory. Using default workspace project instead.
UPDATE src/app/app.module.ts (1996 bytes)
UPDATE angular.json (6815 bytes)
UPDATE src/index.html (1016 bytes)
UPDATE src/styles.css (182 bytes)
Modify Product List Component
list.component.ts
This component does the following:
- Declares a
totalElements
field to hold the total no. of records available in the database. - The
ngOnInit()
method calls
method with page number “0” and size “5” as request parameters.this.
getProducts()
- The
getProducts()
method calls theproductService.getAll()
method and assigns the data and total no. of records from the response to theproducts
andtotalElements
fields respectively. In case of an error, the error message will be displayed in the page. - When the user interacts with the paginator, a
PageEvent
will be fired that can be used to update any associated data view. So, it binds thePageEvent
to thenextPage()
method. This method callsthis.
method with page number and size from thegetProducts()
PageEvent
.
import { Component, OnInit } from '@angular/core';
import { ProductService } from '../_services/product.service';
import { Product } from './product';
import { PageEvent } from '@angular/material/paginator';
@Component({templateUrl: './list.component.html'})
export class ProductListComponent implements OnInit {
products: Product[] = [];
totalElements: number = 0;
constructor(public productService: ProductService) {
}
ngOnInit(): void {
this.getProducts({ page: "0", size: "5" });
}
private getProducts(request) {
this.productService.getAll(request)
.subscribe(data => {
this.products = data['content'];
this.totalElements = data['totalElements'];
}
, error => {
console.log(error.error.message);
}
);
}
nextPage(event: PageEvent) {
const request = {};
request['page'] = event.pageIndex.toString();
request['size'] = event.pageSize.toString();
this.getProducts(request);
}
deleteProduct(id:number){
this.productService.delete(id)
.subscribe(data => {
this.products = this.products.filter(item => item.id !== id);
console.log('Product deleted successfully!');
}
, error => {
console.log(error.error.message);
}
);
}
}
list.component.html
We are using the mat-paginator component from Angular Material for pagination. Each paginator instance requires:
- The number of items per page (default set to 50)
- The total number of items being paged
Page size options
The paginator displays a dropdown of page sizes for the user to choose from. The options for this dropdown can be set via pageSizeOptions
. The current pageSize
will always appear in the dropdown, even if it is not included in pageSizeOptions
.
So we just need to include the mat-paginator with the required options as highlighted below.
<div class="container">
<h5 class="text-center mt-3">PRODUCTS</h5>
<div class="d-flex my-2">
<a href="#" routerLink="/products/add" class="btn btn-outline-success ms-auto">Create New Product</a>
</div>
<div class="table-responsive">
<table class="table table-bordered p-0 m-0">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Description</th>
<th scope="col">Version</th>
<th scope="col">Edition</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<tr scope="row" *ngFor="let product of products">
<td>{{ product.id }}</td>
<td>{{ product.name }}</td>
<td>{{ product.description}}</td>
<td>{{ product.version }}</td>
<td>{{ product.edition }}</td>
<td><a href="#" [routerLink]="['/products/', product.id, 'view']" class="btn btn-info m-1">View</a> <a href="#"
[routerLink]="['/products/', product.id, 'edit']" class="btn btn-primary m-1">Edit</a>
<button type="button" (click)="deleteProduct(product.id)" class="btn btn-danger m-1">Delete</button></td>
</tr>
<tr scope="row" *ngIf="products.length === 0">
<td colspan="6" class="text-center">No record found</td>
</tr>
</tbody>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]" [pageSize]="5" [length]="totalElements" (page)="nextPage($event)"> </mat-paginator>
</div>
</div>
Define Module
app.module.ts
Import and add MatPaginatorModule
in the module declarations
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { MatPaginatorModule } from '@angular/material/paginator';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';
import { BoardAdminComponent } from './board-admin/board-admin.component';
import { BoardModeratorComponent } from './board-moderator/board-moderator.component';
import { BoardUserComponent } from './board-user/board-user.component';
import { TotpComponent } from './totp/totp.component';
import { OrderComponent } from './order/order.component';
import { TokenComponent } from './register/token.component';
import { ProductListComponent } from './products/list.component';
import { ProductViewComponent } from './products/view.component';
import { ProductAddEditComponent } from './products/add-edit.component';
import { authInterceptorProviders } from './_helpers/auth.interceptor';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
RegisterComponent,
HomeComponent,
ProfileComponent,
BoardAdminComponent,
BoardModeratorComponent,
BoardUserComponent,
TotpComponent,
OrderComponent,
TokenComponent,
ProductListComponent,
ProductViewComponent,
ProductAddEditComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
MatPaginatorModule,
BrowserAnimationsModule
],
providers: [authInterceptorProviders],
bootstrap: [AppComponent]
})
export class AppModule { }
Spring Boot Backend Implementation
In the previous tutorial, we have already implemented the /api/products
REST endpoint to return the paginated data with the help of PagingAndSortingRepository
interface provided by Spring Data JPA. Now, we are just gonna see the relevant code here.
The URL of the REST API with the paging parameters will be as shown below. If pageIndex
and size
is not present, then it will default to 0 and 3 respectively.
/api/products?page=1&size=2
Output
{
"content": [
{
"id": 3,
"deleted": false,
"name": "Product 3",
"version": "3.0",
"edition": "2017",
"description": "Product 3"
},
{
"id": 4,
"deleted": false,
"name": "Product 4",
"version": "4.0",
"edition": "2018",
"description": "Product 4"
}
],
"pageable": {
"sort": {
"unsorted": true,
"sorted": false,
"empty": true
},
"offset": 2,
"pageNumber": 1,
"pageSize": 2,
"paged": true,
"unpaged": false
},
"totalPages": 3,
"totalElements": 6,
"last": false,
"size": 2,
"number": 1,
"sort": {
"unsorted": true,
"sorted": false,
"empty": true
},
"numberOfElements": 2,
"first": false,
"empty": false
}
Spring Data Repository
ProductRepository.java
This repository extends PagingAndSortingRepository
in order to get the findAll(Pageable pageable)
and findAll(Sort sort)
methods for paging and sorting.
The findAll(Pageable pageable)
method by default returns a Page<T>
object. A Page<T>
instance, in addition to having the list of Products
, also knows about the total number of available pages by triggering an additional count query.
package com.javachinna.repo;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import com.javachinna.model.Product;
@Repository
public interface ProductRepository extends PagingAndSortingRepository<Product, Long> {
}
Product Service
ProductService.java
package com.javachinna.service;
import com.javachinna.dto.ProductRequest;
import com.javachinna.model.Product;
public interface ProductService extends Service<Product, ProductRequest> {
}
ProductServiceImpl.java
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductRepository productRepository;
@Override
public Page<Product> findAll(Pageable pageable) {
return productRepository.findAll(pageable);
}
}
Product Controller
ProductController.java
The /api/products
endpoint accepts the page number and page size as request parameters with default values 0 & 3 respectively. By passing in the requested page number and the page size, we will create a PageRequest
object, which is an implementation of the Pageable
interface and pass it to the repository method.
@RestController
@RequestMapping(path = "/api/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping
public Page<Product> getProductList(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "3") int size) {
Pageable paging = PageRequest.of(page, size);
return productService.findAll(paging);
}
}
Run Spring Boot App with Maven
You can run the application with mvn clean spring-boot:run
and the REST API services can be accessed via http://localhost:8080
Run the Angular App
You can run this App with the below command and hit the URL http://localhost:8081/ in browser
ng serve --port 8081
Source Code
https://github.com/JavaChinna/angular-spring-boot-pagination
Conclusion
That’s all folks. In this article, we have implemented server-side pagination with Spring Data JPA and Angular Material in our CRUD application.
Thank you for reading.
Nice article, very clear and concise. But missing code for productService.getAll()
Hi Greg, productService.getAll() is there in the
product.service.ts
class.