Angular + Spring Boot + PrimeNG Datatable CRUD Example
In this tutorial, we will implement a PrimeNG datatable CRUD example. We will use Spring Boot for the backend to expose REST APIs.
Join the DZone community and get the full member experience.
Join For FreeIn previous tutorials, we implemented PrimeNG datatable inplace cell editing and PrimeNG datatable row editing. In this tutorial, we will implement a PrimeNG datatable CRUD example. We will use Spring Boot for the backend to expose REST APIs.
Technology Stack
We will be making use of:
- Angular 9
- PrimeNG
- Spring Boot
Video tutorial:
Implementation
We will first implement the Spring Boot backend code to expose the REST APIs for the CRUD operations. In real-world application, Spring Boot will store the information using a database like MySQL. However, for this example, we will be storing the data in a temporary array.
The Spring Boot Maven project will be as follows.
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.codeusingjava</groupId>
<artifactId>spring-boot-rest</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging> <name>spring-boot-rest</name> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Create the Book model class as follows:
xxxxxxxxxx
package com.codeusingjava.model;public class Book { private String name;
private String author;
private int price; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getAuthor() {
return author;
} public void setAuthor(String author) {
this.author = author;
} public int getPrice() {
return price;
} public void setPrice(int price) {
this.price = price;
}}
Create the controller class for exposing the REST APIs. We will be exposing 4 APIs.
xxxxxxxxxx
package com.codeusingjava.controller;import java.util.ArrayList;
import java.util.List;import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import com.codeusingjava.model.Book;
"/api") (
origins = "http://localhost:4200") (
public class BookController { private List<Book> books = createList(); private static List<Book> createList() {
List<Book> bookList = new ArrayList<>(); Book book1 = new Book();
book1.setName("The Godfather");
book1.setAuthor("Mario Puzo");
book1.setPrice(10); Book book2 = new Book();
book2.setName("The Fellowship of the Ring");
book2.setAuthor("J.R.R. Tolkien");
book2.setPrice(15); bookList.add(book1);
bookList.add(book2); return bookList;
} ("/books")
public List<Book> getAllBooks() {
return books;
} ("/books")
public Book createBook( Book book) {
System.out.println("Added Book - " + book.getName());
books.add(book);
return book;
} ("/books/{name}")
public Book updateBook( (value = "name") String name, Book bookDetails) {
System.out.println("Updated Book - " + name);
for (Book book : books) {
if (book.getName().equals(name)) {
books.remove(books.indexOf(book));
books.add(bookDetails);
break;
}
}
return bookDetails;
} ("/books/{name}")
public Book deleteBook( (value = "name") String name) {
System.out.println("Deleted Book - " + name);
Book deletedBook = null;
for (Book book : books) {
if (book.getName().equals(name)) {
books.remove(book);
deletedBook = book;
break;
}
}
return deletedBook;
}
}
Finally, create the Spring Boot bootstrap class for starting the application:
xxxxxxxxxx
package com.codeusingjava;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
public class BooksApplication { public static void main(String[] args) {
SpringApplication.run(BooksApplication.class, args);
}
}
Develop Angular Application
In a previous tutorial, we implemented a PrimeNG row editing example. We will be modifying this example perform CRUD operations using the PrimeNG datatable.
Download the example.
Run the following command to build the project: npm install
.
Start the project as follows: ng serve
.
If we now go to localhost:4200/books, we can see the list of books populated in the PrimeNG datatable. This books data is currently hardcoded and being fetched from the books.json file in the assets folder.
We will first be implementing the GET
functionality to get the books list from the Spring Boot backend to populate the datatable.
Modify the book.service.ts to make a GET
call to the Spring Boot backend.
x
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';export interface Book {
name;
price;
author;
} ({
providedIn: 'root'
})
export class BookService { constructor(private http: HttpClient) {} getBooks() {
return this.http.get<Book[]>('http://localhost:8080/api/books');
}
}
Above, the GET
call returns an observable of Book array, which we need to subscribe to in the book component file.
x
import { Component, OnInit } from '@angular/core';
import { BookService, Book } from '../service/book.service'; ({
selector: 'app-book-data',
templateUrl: './book-data.component.html',
styleUrls: ['./book-data.component.css']
})
export class BookDataComponent implements OnInit { books: Book[]; constructor(private bookService: BookService) { } ngOnInit() {
this.bookService.getBooks().
subscribe(books => this.books = books);
} onRowEditInit(book: Book) {
console.log('Row edit initialized');
} onRowEditSave(book: Book) {
console.log('Row edit saved');
} onRowEditCancel(book: Book, index: number) {
console.log('Row edit cancelled');
}}
Start the Spring Boot application and go to localhost:4200/books to get the list of books from the Spring Boot backend.
Next, we will be implementing the update Book functionality. We previously already added the edit icons in the data table and implemented the row edit functionality. Now, for the successful edit save, we will want to make a call the Spring Boot backend to update the book data. Also, if the edit is cancelled, we will want to revert the edit made, so we will be cloning the book list before the init
is initialized to store the original book list and use it if the edit is cancelled.
Modify the book.service.ts to make a PUT
call to the spring boot backend with the updated book data.
x
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';export interface Book {
name;
price;
author;
} ({
providedIn: 'root'
})
export class BookService { constructor(private http: HttpClient) {} getBooks() {
return this.http.get<Book[]>('http://localhost:8080/api/books');
} public updateBook(book) {
return this.http.put<Book>("http://localhost:8080/api/books" + "/"+ book.name,book);
}
}
We will be making this update call when the edit is to be saved successfully, i.e. in the onRowEditSave
method. Also, if the edit reverted, we will need the original data to be displayed again. So, we will do:
x
import { Component, OnInit } from '@angular/core';
import { BookService, Book } from '../service/book.service'; ({
selector: 'app-book-data',
templateUrl: './book-data.component.html',
styleUrls: ['./book-data.component.css']
})
export class BookDataComponent implements OnInit { books: Book[]; constructor(private bookService: BookService) { } ngOnInit() {
this.bookService.getBooks().
subscribe(books => this.books = books);
} clonedBooks: { [s: string]: Book; } = {};
onRowEditInit(book: Book) {
console.log('Row edit initialized');
this.clonedBooks[book.name] = { ...book };
} onRowEditSave(book: Book) {
console.log('Row edit saved');
this.bookService.updateBook(book)
.subscribe( data => {
this.ngOnInit();
alert("Book Updated successfully.");
});
} onRowEditCancel(book: Book, index: number) {
console.log('Row edit cancelled');
this.books[index] = this.clonedBooks[book.name];
delete this.clonedBooks[book.name];
}}
Go to localhost:4200 and test the edit Book functionality.
Next, we will add the delete Book functionality.
Modify book.service.ts to make a DELETE
call to the Spring Boot backend.
x
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';export interface Book {
name;
price;
author;
} ({
providedIn: 'root'
})
export class BookService { constructor(private http: HttpClient) {} getBooks() {
return this.http.get<Book[]>('http://localhost:8080/api/books');
} public updateBook(book) {
return this.http.put<Book>("http://localhost:8080/api/books" + "/"+ book.name,book);
}
public deleteBook(book) {
return this.http.delete<Book>("http://localhost:8080/api/books" + "/"+ book.name);
}
}
In the Book component file, add the delete
function, which should be called when we want to delete a book from the table:
x
import { Component, OnInit } from '@angular/core';
import { BookService, Book } from '../service/book.service'; ({
selector: 'app-book-data',
templateUrl: './book-data.component.html',
styleUrls: ['./book-data.component.css']
})
export class BookDataComponent implements OnInit { books: Book[]; constructor(private bookService: BookService) { } ngOnInit() {
this.bookService.getBooks().
subscribe(books => this.books = books);
} clonedBooks: { [s: string]: Book; } = {};
onRowEditInit(book: Book) {
console.log('Row edit initialized');
this.clonedBooks[book.name] = { ...book };
} onRowEditSave(book: Book) {
console.log('Row edit saved');
this.bookService.updateBook(book)
.subscribe( data => {
this.ngOnInit();
alert("Book Updated successfully.");
});
} onRowEditCancel(book: Book, index: number) {
console.log('Row edit cancelled');
this.books[index] = this.clonedBooks[book.name];
delete this.clonedBooks[book.name];
} deleteBook(book: Book) {
console.log('Book Deleted');
this.bookService.deleteBook(book)
.subscribe( data => {
this.ngOnInit();
alert("Book Deleted successfully.");
});
}}
If we now go to localhost:4200/books, we will see the delete icon, which will delete the book upon clicking.
We will now implement the add Book functionality.
Modify the book.service.ts to make a POST
call to the Spring Boot backend.
xxxxxxxxxx
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http';
export interface Book { name; price; author; }
providedIn: 'root' }) export class BookService { constructor(private http: HttpClient) {} getBooks() { return this.http.get<Book[]>('http://localhost:8080/api/books'); } public updateBook(book) { return this.http.put<Book>("http://localhost:8080/api/books" + "/"+ book.name,book); } public deleteBook(book) { return this.http.delete<Book>("http://localhost:8080/api/books" + "/"+ book.name); } public createBook(book) { return this.http.post<Book>("http://localhost:8080/api/books", book); } ({
}
We will be adding an Add Button in the HTML page for adding a new book. For this, we will need to get the book information from the user. We will get this information using an Angular form, which we will show in a dialog box.
We will first need to import the required modules in the app.module.ts file:
xxxxxxxxxx
<p-table [value]="books" dataKey="name" editMode="row">
<ng-template pTemplate="caption">
List of Books
<p-button class="btnAdd" label="ADD" (onClick)="onBookAdd()"></p-button>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th>Name</th>
<th>Author</th>
<th>Price</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-rowData let-editing="editing" let-ri="rowIndex">
<tr [pEditableRow]="rowData">
<td>
{{rowData.name}}
</td>
<td>
<p-cellEditor>
<ng-template pTemplate="input">
<input pInputText type="text" [(ngModel)]="rowData.author">
</ng-template>
<ng-template pTemplate="output">
{{rowData.author}}
</ng-template>
</p-cellEditor>
</td>
<td>
<p-cellEditor>
<ng-template pTemplate="input">
<input pInputText type="text" [(ngModel)]="rowData.price">
</ng-template>
<ng-template pTemplate="output">
{{rowData.price}}
</ng-template>
</p-cellEditor>
</td>
<td style="text-align:center">
<p-button class="btnAdd" icon="pi pi-trash" class="ui-button-info" style="margin-right: .5em" (onClick)="deleteBook(rowData)"></p-button> <button *ngIf="!editing" pButton type="button" pInitEditableRow icon="pi pi-pencil"
class="ui-button-info" (click)="onRowEditInit(rowData)"></button>
<button *ngIf="editing" pButton type="button" pSaveEditableRow icon="pi pi-check"
class="ui-button-success" style="margin-right: .5em" (click)="onRowEditSave(rowData)"></button>
<button *ngIf="editing" pButton type="button" pCancelEditableRow icon="pi pi-times"
class="ui-button-danger" (click)="onRowEditCancel(rowData, ri)"></button>
</td>
</tr>
</ng-template>
</p-table><p-dialog header="Book Details" [(visible)]="displayDialog"
[responsive]="true" showEffect="fade" [modal]="true">
<form #BookForm="ngForm" novalidate>
<div class="ui-grid ui-grid-responsive ui-fluid" *ngIf="bookForDialog">
<div class="ui-g ui-g-12 ui-g-nopad">
<div class="ui-g-12 ui-md-3 ui-label">
<label for="fname">Book Name</label>
</div>
<div class="ui-g-12 ui-md-9">
<input pInputText id="fname" name="fname" required
[(ngModel)]="bookForDialog.name"/>
</div>
</div>
<div class="ui-g ui-g-12 ui-g-nopad">
<div class="ui-g-12 ui-md-3 ui-label">
<label for="lname">Author</label>
</div>
<div class="ui-g-12 ui-md-9">
<input pInputText id="lname" name="lname" required
[(ngModel)]="bookForDialog.author"/>
</div>
</div>
<div class="ui-g ui-g-12 ui-g-nopad">
<div class="ui-g-12 ui-md-3 ui-label">
<label for="prof">Price</label>
</div>
<div class="ui-g-12 ui-md-9">
<input pInputText id="prof" name="prof" required
[(ngModel)]="bookForDialog.price"/>
</div>
</div>
</div>
</form>
<p-footer>
<div class="ui-dialog-buttonpane ui-helper-clearfix">
<button type="submit" pButton icon="fa-check" (click)="saveBook()"
label="Save" [disabled]="!BookForm.form.valid"></button>
</div>
</p-footer>
</p-dialog>
We are done with the changes. Now, when the user clicks on the Add button, a form will be displayed. When the user clicks on the Save button, the book will be added.
Opinions expressed by DZone contributors are their own.
Comments