o7planning

CRUD Example with Spring Boot, REST and AngularJS

  1. Objective of Lesson
  2. Create Spring Boot project
  3. Model, DAO
  4. Controller, Rest Controller
  5. Javascript, Css, View
  6. Explain the principle of operation
  7. Bonus: success and error functions

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:
  1. success & error functions with 1 parameter.
  2. 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

Show More