Angular — Spring Simple CRUD Application
In this tutorial, we will build a CRUD (create-read-update-delete) web application.
We will use Spring Boot to handle the back-end part and Angular to handle the front-end part, and they will communicate with each other using a REST API. Then on the back end, we will have a database and we will have full CRUD support for the database.
Working of Application
- Once we deployed our application on the server, a product form generates at the web browser.
- The form facilitates to add and view products.
- On clicking, add product button, the page display a form where we can add a product by filling the required details and submit them.
- Using view product link, we can fetch the details of the products list. Here, each product also contains update and delete link.
- Therefore, we can update the details of the product and delete them from the database.
- Once completed, provide the URL http://localhost:4200/ at the web browser.
Tools to be used
- Use any IDE to develop the Spring Boot project. It may be STS/IntelliJ/Eclipse… Here, we are using STS (Spring Tool Suite).
- MySQL for the database.
- Use any IDE to develop the Angular project. It may be Visual Studio Code/Sublime. Here, we are using Visual Studio Code.
- Server: Tomcat.
Create Database
Let’s create a database “crud-tuto”. There is no need to create a table as Hibernate automatically created it.
Spring Boot Application
First, we need to create a new Spring Starter project with the following structure
We have to add these dependencies to our pom.xml file, then execute maven update :
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.9</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>tn.spring</groupId>
<artifactId>crud-tuto</artifactId>
<version>1.0</version>
<name>crud-tuto</name>
<description>Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
Next step, we have to develop the entity for the product.
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Builder
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id_product;
private String title;
private String price;
private String quantity;
}
Since we will require basic CRUD functionality on Product entity, we should define a ProductRepository interface as well:
public interface ProductRepository extends JpaRepository<Product, Long> {}
Next, we will create the service layer using the annotation @Service.
@Service
@AllArgsConstructor
@Slf4j
public class ProductServiceImpl implements IProductService {
private final ProductRepository productRepository;
@Override
public Product addProduct(Product p) {
if (p.getTitle() == null || p.getTitle().isEmpty()) {
throw new IllegalArgumentException("Product title cannot be empty");
}
try {
return productRepository.save(p);
} catch (Exception e) {
throw new RuntimeException("Failed to add product", e);
}
}
@Override
public Product editProduct(Product p) throws RuntimeException {
if (p.getId_product() == null) {
throw new IllegalArgumentException("Product ID cannot be null");
}
if (p.getTitle() == null || p.getTitle().isEmpty()) {
throw new IllegalArgumentException("Product title cannot be empty");
}
try {
return productRepository.save(p);
} catch (Exception e) {
throw new RuntimeException("Failed to update product", e);
}
}
@Override
public void deleteProduct(Long idProduct) {
Optional<Product> product = productRepository.findById(idProduct);
product.ifPresent(p -> {
productRepository.delete(p);
log.info("Product with id " + idProduct + " has been deleted");
});
}
@Override
public List<Product> retrieveAll() {
return productRepository.findAll();
}
}
@AllArgsConstructor inject the repository in the service.
These methods we implemented will call methods already existing in the repository layer.
Now, let’s implement the REST API. In this case, it’s just a simple REST controller.
@CrossOrigin(origins = "http://localhost:4200")
@RestController
@AllArgsConstructor
public class ProductRestController {
private final IProductService productService;
// http://localhost:8081/all-products
@GetMapping("/all-products")
public List<Product> getProducts() {
return productService.retrieveAll();
}
// http://localhost:8081/add-product
@PostMapping("/add-product")
public Product addProduct(@RequestBody Product p) {
return productService.addProduct(p);
}
// http://localhost:8081/edit-product
@PutMapping("/edit-product")
public Product editProduct(@RequestBody Product p) {
return productService.editProduct(p);
}
// http://localhost:8081/delet-product/id
@DeleteMapping("/delete-product/{idProduct}")
public void deleteProduct(@PathVariable("idProduct") Long id) {
productService.deleteProduct(id);
}
}
Nothing is complex in the definition of the ProductRestController class.
The only implementation detail worth noting here, of course, is the use of the @CrossOrigin annotation. As the name implies, this annotation allows cross-origin resource sharing (CORS) on the server.
This annotation is necessary here, because we deploy our Angular frontend to http://localhost:4200 and our backend to http://localhost:8089, the browser would otherwise deny requests from one to the other.
The Angular Application
In this tutorial, we are using the following versions :
- Angular CLI : 12.0.1
- Node : 14.17.0
- npm : 6.14.13
First we create our front project using the command : ng new ProjectName.
In order to use bootstrap in our project :
- We use the following command : npm install bootstrap
- Then we have to add “node_modules/bootstrap/dist/css/bootstrap.css” in angular.json file for styles.
Since our Angular application will fetch from and persist Product entity in the database, let’s implement a simple model with TypeScript using :
- ng generate class product
export class Product {
id_product : any;
title : any;
price : any;
quantity : any;
}
The aim of this class is to mapping the specified fields to the fields of the Spring entity class.
Now that our client-side domain Product class is already defined, let’s implement a service class that executes the defined requests to the http://localhost:8089 endpoint.
@Injectable({
providedIn: 'root'
})
export class ProductService {
readonly API_URL = 'http://localhost:8089';
constructor(private httpClient: HttpClient) { }
getAllProducts() {
return this.httpClient.get(`${this.API_URL}/all-products`)
}
addProduct(product : any) {
return this.httpClient.post(`${this.API_URL}/add-product`, product)
}
editProduct(product : any){
return this.httpClient.put(`${this.API_URL}/edit-product`, product)
}
deleteProduct(idProduct : any){
return this.httpClient.delete(`${this.API_URL}/delete-product/${idProduct}`)
}
}
This will allow us to encapsulate access to the REST controller in a single class, which we can reuse throughout the entire application.
In this case, the ProductService class is the thin middle-tier between the REST service and the application’s presentation layer. Therefore, we need to define a component responsible for rendering the list of Product entity persisted in the database.
Let’s open a terminal console, then generate a product component:
- ng generate component product
Angular CLI will generate an empty component class that implements the ngOnInit interface. The interface declares a hook ngOnInit() method, which Angular calls after it has finished instantiating the implementing class, and after calling its constructor, too.
Let’s refactor the class so that it can take a ProductService instance in the constructor :
Additionally, we need to edit the component’s HTML file, product.component.html, to create the table that displays the list of entity :
Notice the use of the *ngFor directive. The directive is called a repeater, and we can use it for iterating over the contents of a variable and iteratively rendering HTML elements. In this case, we used it for dynamically rendering the table’s rows.
In addition, we used variable interpolation for showing the title, price and quantity of each product.
In order to persist a new product in the database, we need to to edit product.component.html file and create HTML form.
The ngModel directive gives us two-way data binding functionality between the form controls and the client-side domain model — the User class.
This means that data entered in the form input fields will flow to the model — and the other way around. Changes in both elements will be reflected immediately via DOM manipulation.
Next, we need to edit the app.module.ts file, so Angular can import all the required modules, components, and services.
Running the Application
Finally, we’re ready to run our application.
To accomplish this, let’s first run the Spring Boot application, so the REST service is alive and listening for requests.
Once the Spring Boot application has been started, let’s open a command console and type the following command:
- ng serve — open
This will start Angular’s live development server and also open the browser at http://localhost:4200.
By clicking on “Add product” the following form displayed to add a new product.
Conclusion
In this tutorial, we learned how to build a basic web application with Spring Boot and Angular.
All the code samples shown in this tutorial are available over on GitHub.