CRUD Example with Spring Boot, REST and AngularJS
1. Objective of Lesson
In this lesson, I will show you how to create a simple application that combines Spring Boot, Rest and AngularJS technologies. First of all, you can preview the application which we are going to exercise:
AngularJS is an open source library. It is based on Javascript and helps you build Single Page applications. In this lesson, we are going to create a page that displays the list of employees, and allows you to add, delete, and edit employees.
The issues that will be mentioned in this lesson:
- Create a Spring Boot application.
- Create REST APIs with the functions: Querying, creating, editing, deleting data
- AngularJS calls REST APIs to query data and display the data on the interface. AngularJS calls the REST APIs to create, delete, and edit the data.
At the end of this lesson, we will run the application and explain the operating principle of AngularJS in each specific function.
2. Create Spring Boot project
On the Eclipse, create a Spring Boot project:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>org.o7planning</groupId>
<artifactId>SpringBootAngularJS</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBootAngularJS</name>
<description>Spring Boot + AngularJS</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
spring.thymeleaf.cache=false
SpringBootAngularJsApplication.java
package org.o7planning.sbangularjs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootAngularJsApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAngularJsApplication.class, args);
}
}
3. Model, DAO
Employee.java
package org.o7planning.sbangularjs.model;
public class Employee {
private Long empId;
private String empNo;
private String empName;
private String position;
public Employee() {
}
public Employee(EmployeeForm empForm) {
this.empId = empForm.getEmpId();
this.empNo = empForm.getEmpNo();
this.empName = empForm.getEmpName();
this.position = empForm.getPosition();
}
public Employee(Long empId, String empNo, String empName, String position) {
this.empId = empId;
this.empNo = empNo;
this.empName = empName;
this.position = position;
}
public Long getEmpId() {
return empId;
}
public void setEmpId(Long empId) {
this.empId = empId;
}
public String getEmpNo() {
return empNo;
}
public void setEmpNo(String empNo) {
this.empNo = empNo;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
}
EmployeeForm.java
package org.o7planning.sbangularjs.model;
public class EmployeeForm {
private Long empId;
private String empNo;
private String empName;
private String position;
public Long getEmpId() {
return empId;
}
public void setEmpId(Long empId) {
this.empId = empId;
}
public String getEmpNo() {
return empNo;
}
public void setEmpNo(String empNo) {
this.empNo = empNo;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
}
EmployeeDAO.java
package org.o7planning.sbangularjs.dao;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.o7planning.sbangularjs.model.Employee;
import org.o7planning.sbangularjs.model.EmployeeForm;
import org.springframework.stereotype.Repository;
@Repository
public class EmployeeDAO {
private static final Map<Long, Employee> empMap = new HashMap<Long, Employee>();
static {
initEmps();
}
private static void initEmps() {
Employee emp1 = new Employee(1L, "E01", "Smith", "Clerk");
Employee emp2 = new Employee(2L, "E02", "Allen", "Salesman");
Employee emp3 = new Employee(3L, "E03", "Jones", "Manager");
empMap.put(emp1.getEmpId(), emp1);
empMap.put(emp2.getEmpId(), emp2);
empMap.put(emp3.getEmpId(), emp3);
}
public Long getMaxEmpId() {
Set<Long> keys = empMap.keySet();
Long max = 0L;
for (Long key : keys) {
if (key > max) {
max = key;
}
}
return max;
}
public Employee getEmployee(Long empId) {
return empMap.get(empId);
}
public Employee addEmployee(EmployeeForm empForm) {
Long empId= this.getMaxEmpId()+ 1;
empForm.setEmpId(empId);
Employee newEmp = new Employee(empForm);
empMap.put(newEmp.getEmpId(), newEmp);
return newEmp;
}
public Employee updateEmployee(EmployeeForm empForm) {
Employee emp = this.getEmployee(empForm.getEmpId());
if(emp!= null) {
emp.setEmpNo(empForm.getEmpNo());
emp.setEmpName(empForm.getEmpName());
emp.setPosition(empForm.getPosition());
}
return emp;
}
public void deleteEmployee(Long empId) {
empMap.remove(empId);
}
public List<Employee> getAllEmployees() {
Collection<Employee> c = empMap.values();
List<Employee> list = new ArrayList<Employee>();
list.addAll(c);
return list;
}
}
4. Controller, Rest Controller
MainController.java
package org.o7planning.sbangularjs.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MainController {
@RequestMapping("/")
public String welcome() {
return "index";
}
}
MainRESTController.java
package org.o7planning.sbangularjs.controller;
import java.util.List;
import org.o7planning.sbangularjs.dao.EmployeeDAO;
import org.o7planning.sbangularjs.model.Employee;
import org.o7planning.sbangularjs.model.EmployeeForm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MainRESTController {
@Autowired
private EmployeeDAO employeeDAO;
// URL:
// http://localhost:8080/SomeContextPath/employees
// http://localhost:8080/SomeContextPath/employees.xml
// http://localhost:8080/SomeContextPath/employees.json
@RequestMapping(value = "/employees", //
method = RequestMethod.GET, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public List<Employee> getEmployees() {
List<Employee> list = employeeDAO.getAllEmployees();
return list;
}
// URL:
// http://localhost:8080/SomeContextPath/employee/{empId}
// http://localhost:8080/SomeContextPath/employee/{empId}.xml
// http://localhost:8080/SomeContextPath/employee/{empId}.json
@RequestMapping(value = "/employee/{empId}", //
method = RequestMethod.GET, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public Employee getEmployee(@PathVariable("empId") Long empId) {
return employeeDAO.getEmployee(empId);
}
// URL:
// http://localhost:8080/SomeContextPath/employee
// http://localhost:8080/SomeContextPath/employee.xml
// http://localhost:8080/SomeContextPath/employee.json
@RequestMapping(value = "/employee", //
method = RequestMethod.POST, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public Employee addEmployee(@RequestBody EmployeeForm empForm) {
System.out.println("(Service Side) Creating employee with empNo: " + empForm.getEmpNo());
return employeeDAO.addEmployee(empForm);
}
// URL:
// http://localhost:8080/SomeContextPath/employee
// http://localhost:8080/SomeContextPath/employee.xml
// http://localhost:8080/SomeContextPath/employee.json
@RequestMapping(value = "/employee", //
method = RequestMethod.PUT, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public Employee updateEmployee(@RequestBody EmployeeForm empForm) {
System.out.println("(Service Side) Editing employee with Id: " + empForm.getEmpId());
return employeeDAO.updateEmployee(empForm);
}
// URL:
// http://localhost:8080/SomeContextPath/employee/{empId}
@RequestMapping(value = "/employee/{empId}", //
method = RequestMethod.DELETE, //
produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public void deleteEmployee(@PathVariable("empId") Long empId) {
System.out.println("(Service Side) Deleting employee with Id: " + empId);
employeeDAO.deleteEmployee(empId);
}
}
5. Javascript, Css, View
index.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>AngularJS</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.js"></script>
<script th:src="@{/main.js}"></script>
<link th:href="@{/main.css}" rel="stylesheet" />
<head>
<body ng-app="EmployeeManagement" ng-controller="EmployeeController">
<h3>
CRUD: Spring Boot + Rest + AngularJS
</h3>
<form ng-submit="submitEmployee()">
<table border="0">
<tr>
<td>Emp Id</td>
<td>{{employeeForm.empId}}</td>
</tr>
<tr>
<td>Emp No</td>
<td><input type="text" ng-model="employeeForm.empNo" /></td>
</tr>
<tr>
<td>Emp Name</td>
<td><input type="text" ng-model="employeeForm.empName" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Submit" class="blue-button" />
</td>
</tr>
</table>
</form>
<br/>
<a class="create-button" ng-click="createEmployee()">Create Employee</a>
<table border="1">
<tr>
<th>Emp Id</th>
<th>Emp No</th>
<th>Emp Name</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<!-- $scope.employees -->
<tr ng-repeat="employee in employees">
<td> {{ employee.empId }}</td>
<td> {{ employee.empNo }}</td>
<td >{{ employee.empName }}</td>
<td>
<a ng-click="editEmployee(employee)" class="edit-button">Edit</a>
</td>
<td>
<a ng-click="deleteEmployee(employee)" class="delete-button">Delete</a>
</td>
</tr>
</table>
</body>
</html>
main.css
table {
border-collapse: collapse;
}
table td, th {
padding: 5px;
}
.create-button {
color: blue;
cursor: pointer;
padding: 5px;
}
.edit-button {
padding: 2px 5px;
background: #25A6E1;
cursor: pointer;
}
.delete-button {
padding: 2px 5px;
background: #CD5C5C;
cursor: pointer;
}
main.js
var app = angular.module("EmployeeManagement", []);
// Controller Part
app.controller("EmployeeController", function($scope, $http) {
$scope.employees = [];
$scope.employeeForm = {
empId: 1,
empNo: "",
empName: ""
};
// Now load the data from server
_refreshEmployeeData();
// HTTP POST/PUT methods for add/edit employee
// Call: http://localhost:8080/employee
$scope.submitEmployee = function() {
var method = "";
var url = "";
if ($scope.employeeForm.empId == -1) {
method = "POST";
url = '/employee';
} else {
method = "PUT";
url = '/employee';
}
$http({
method: method,
url: url,
data: angular.toJson($scope.employeeForm),
headers: {
'Content-Type': 'application/json'
}
}).then(_success, _error);
};
$scope.createEmployee = function() {
_clearFormData();
}
// HTTP DELETE- delete employee by Id
// Call: http://localhost:8080/employee/{empId}
$scope.deleteEmployee = function(employee) {
$http({
method: 'DELETE',
url: '/employee/' + employee.empId
}).then(_success, _error);
};
// In case of edit
$scope.editEmployee = function(employee) {
$scope.employeeForm.empId = employee.empId;
$scope.employeeForm.empNo = employee.empNo;
$scope.employeeForm.empName = employee.empName;
};
// Private Method
// HTTP GET- get all employees collection
// Call: http://localhost:8080/employees
function _refreshEmployeeData() {
$http({
method: 'GET',
url: '/employees'
}).then(
function(res) { // success
$scope.employees = res.data;
},
function(res) { // error
console.log("Error: " + res.status + " : " + res.data);
}
);
}
function _success(res) {
_refreshEmployeeData();
_clearFormData();
}
function _error(res) {
var data = res.data;
var status = res.status;
var header = res.header;
var config = res.config;
alert("Error: " + status + ":" + data);
}
// Clear the form
function _clearFormData() {
$scope.employeeForm.empId = -1;
$scope.employeeForm.empNo = "";
$scope.employeeForm.empName = ""
};
});
6. Explain the principle of operation
OK, now, you can run the application and view how the AngularJS works in each specific function.
The function of displaying the list of employees:
When the website is run, the AngularJS will call the REST APIs to take the list of employees. This data is stored in the $scope.employees variable. And the AngularJS will display it on the interface. If the data of $scope.employees change, the AngularJS will automatically update the interface.
The AngularJS calls the REST API to take the list of employees and store the taken data in the $scope.employees variable.
// Private Method
// HTTP GET- get all employees collection
// Call: http://localhost:8080/employees
function _refreshEmployeeData() {
$http({
method: 'GET',
url: '/employees'
}).then(
function(res) { // success
$scope.employees = res.data;
},
function(res) { // error
console.log("Error: " + res.status + " : " + res.data);
}
);
}
.....
The AngularJS displays data in the $scope.employeesvariable onto the interface:
<table border="1">
<tr>
<th>Emp Id</th>
<th>Emp No</th>
<th>Emp Name</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<!-- $scope.employees -->
<tr ng-repeat="employee in employees">
<td> {{ employee.empId }}</td>
<td> {{ employee.empNo }}</td>
<td >{{ employee.empName }}</td>
<td>
<a ng-click="editEmployee(employee)" class="edit-button">Edit</a>
</td>
<td>
<a ng-click="deleteEmployee(employee)" class="delete-button">Delete</a>
</td>
</tr>
</table>
The function of editing employee's information:
The AngularJS can be two-way data binding between the Model and the View. This means if an user enters data in the View, this data will be automatically updated in the Model and vice versa if the data in the Model changes, it will be displayed in the View.
AngularJS uses the ng-model attribute for 2-way binding between model and view:
<form ng-submit="submitEmployee()">
<table border="0">
<tr>
<td>Emp Id</td>
<td>{{employeeForm.empId}}</td>
</tr>
<tr>
<td>Emp No</td>
<td><input type="text" ng-model="employeeForm.empNo" /></td>
</tr>
<tr>
<td>Emp Name</td>
<td><input type="text" ng-model="employeeForm.empName" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Submit" class="blue-button" />
</td>
</tr>
</table>
</form>
The user press "Edit", the $scope.editEmployee function will be called
<a ng-click="editEmployee(employee)" class="edit-button">Edit</a>
Javascript:
// JavaScript: When user Click to Edit button:
$scope.editEmployee = function(employee) {
$scope.employeeForm.empId = employee.empId;
$scope.employeeForm.empNo = employee.empNo;
$scope.employeeForm.empName = employee.empName;
};
// SAVE !!
// HTTP POST/PUT methods for add/edit employee
// Call: http://localhost:8080/employee
$scope.submitEmployee = function() {
var method = "";
var url = "";
if ($scope.employeeForm.empId == -1) {
method = "POST";
url = '/employee';
} else {
method = "PUT";
url = '/employee';
}
$http({
method: method,
url: url,
data: angular.toJson($scope.employeeForm),
headers: {
'Content-Type': 'application/json'
}
}).then(_success, _error);
};
Funtion of deleting employees:
To delete an employee, the AngularJS calls REST API to request to delete an employee. In case of success, it continues to call the REST API to query the list of employees and display on the interface.
// HTTP DELETE- delete employee by Id
// Call: http://localhost:8080/employee/{empId}
$scope.deleteEmployee = function(employee) {
$http({
method: 'DELETE',
url: '/employee/' + employee.empId
}).then(_success, _error);
};
.....
function _success(res) {
_refreshEmployeeData();
_clearFormData();
}
function _error(res) {
var data = res.data;
var status = res.status;
var header = res.header;
var config = res.config;
alert("Error: " + status + ":" + data);
}
7. Bonus: success and error functions
In AngularJS there are two ways to use the success and error functions:
- success & error functions with 1 parameter.
- success & error functions with 4 parameters.
- success & error with 1 parameter:
$http.get('/someURL').then(
// Success
function(response) {
var data = response.data;
var status = response.status;
var header = response.header;
var config = response.config;
// ...
},
// Error
function(response) {
var data = response.data;
var status = response.status;
var header = response.header;
var config = response.config;
// ...
}
);
- success & error with 4 parameters:
$http.get('/someURL')
// Success
.success(
function(data, status, header, config) {
// ...
}
)
// Error
.error(
function(data, status, header, config) {
// error handler
}
);
Spring Boot Tutorials
- Install Spring Tool Suite for Eclipse
- Spring Tutorial for Beginners
- Spring Boot Tutorial for Beginners
- Spring Boot Common Properties
- Spring Boot and Thymeleaf Tutorial with Examples
- Spring Boot and FreeMarker Tutorial with Examples
- Spring Boot and Groovy Tutorial with Examples
- Spring Boot and Mustache Tutorial with Examples
- Spring Boot and JSP Tutorial with Examples
- Spring Boot, Apache Tiles, JSP Tutorial with Examples
- Use Logging in Spring Boot
- Application Monitoring with Spring Boot Actuator
- Create a Multi Language web application with Spring Boot
- Use multiple ViewResolvers in Spring Boot
- Use Twitter Bootstrap in Spring Boot
- Spring Boot Interceptors Tutorial with Examples
- Spring Boot, Spring JDBC and Spring Transaction Tutorial with Examples
- Spring JDBC Tutorial with Examples
- Spring Boot, JPA and Spring Transaction Tutorial with Examples
- Spring Boot and Spring Data JPA Tutorial with Examples
- Spring Boot, Hibernate and Spring Transaction Tutorial with Examples
- Integrating Spring Boot, JPA and H2 Database
- Spring Boot and MongoDB Tutorial with Examples
- Use Multiple DataSources with Spring Boot and JPA
- Use Multiple DataSources with Spring Boot and RoutingDataSource
- Create a Login Application with Spring Boot, Spring Security, Spring JDBC
- Create a Login Application with Spring Boot, Spring Security, JPA
- Create a User Registration Application with Spring Boot, Spring Form Validation
- Example of OAuth2 Social Login in Spring Boot
- Run background scheduled tasks in Spring
- CRUD Restful Web Service Example with Spring Boot
- Spring Boot Restful Client with RestTemplate Example
- CRUD Example with Spring Boot, REST and AngularJS
- Secure Spring Boot RESTful Service using Basic Authentication
- Secure Spring Boot RESTful Service using Auth0 JWT
- Spring Boot File Upload Example
- Spring Boot File Download Example
- Spring Boot File Upload with jQuery Ajax Example
- Spring Boot File Upload with AngularJS Example
- Create a Shopping Cart Web Application with Spring Boot, Hibernate
- Spring Email Tutorial with Examples
- Create a simple Chat application with Spring Boot and Websocket
- Deploy Spring Boot Application on Tomcat Server
- Deploy Spring Boot Application on Oracle WebLogic Server
- Install a free Let's Encrypt SSL certificate for Spring Boot
- Configure Spring Boot to redirect HTTP to HTTPS
- Fetch data with Spring Data JPA DTO Projections
Show More