Exemple CRUD avec Spring Boot, REST et AngularJS

View more Tutorials:

1- Objectif de la leçon

Dans cette leçon, je vous donnerai des instructions de la création d'une application simple en combinant les technologies  Spring Boot, Rest et  AngularJS. Mais tout d'abord, vous pouvez voir l'application avec laquelle  que vous allez pratiquer :
AngularJS est une bibliothèque de la ressource ouverte, elle est créée sur la base de  Javascript, et vous aide de construire des applications  Single Page (une seule page). Dans ce poste, nous allons créer une seule page. Cette page affiche la liste des employés et elle vous permet également d'ajouter, de supprimer, de modifier des employés.
Les problèmes qui seront mentionnés dans cette publication :
  1. Créer une application Spring Boot.
  2. Créer des REST API avec des fonctionnalités : le requêtage, la création, la modification, la suppression des données.
  3. AngularJS appelle des REST API pour requêter des données ainsi qu'afficher des données sur l'interface. AngularJS appelle des REST API pour créer, supprimer et modifier des données.
A la fin de cette leçon, nous allons exécuter l'application et expliquer les règles de fonctionnement de  AngularJS dans chaque fonctionnalité précise.

2- Créer un projet Spring Boot

Sur  Eclipse créez un projet  Spring Boot:
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- Explication du principe de l'opération

OK, maintenant, vous pouvez exécuter l'appliction et voir comment  AngularJS travaille dans chaque fonctionnalité précise.

La fonctionnalité d'affichage la liste des employés :

Lorsque la page du web fonctionne, AngularJS appelle  REST API afin de retirer la liste des employés, ces données ont été stockées sur la variable  $scope.employees. Et  AngularJS va l'afficher sur l'interface. Si les données  $scope.employees change,  AngularJS va automatiquement mettre à jour l'interface.
AngularJS appelle  REST API afin de retirer la liste des employés (Employee) et de stocker ces données sur la variable  $scope.employees.
// 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);
        }
    );
}

.....
AngularJS affiche des données de la variable  $scope.employees sur l'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>

La fonctionnnalité de modification l'information de l'employés:

AngularJS peut être une liaison de données à double-sens (2-way binding) entre  Model et  View. Ceci signifie que si l'utilisateur saisit des données dans  View, ces données seront mise à jour sur  Model, et à l'inverse si les données sur  Model changent, elles seront affichées sur  View.
AngularJS utilise l'attribut (attribute) ng-model pour la liaison de données à double-sens (2-way binding) entre  Model et  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>
Lorsque l'utilisateur appuie  "Edit", la fonction  $scope.editEmployee sera appelée.
<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);
};

La fonctionnalité de suppression des employés (Employee):

Afin de supprimer un employé (Employee) AngularJS appelle  REST API pour supprimer un employé. En cas de succès (success) il continue d'appeler  REST API afin de requêter la liste des employés et de l'afficher sur l'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- Prime : La fonction success et error

Dans  AngularJS il y a deux manières d'utiliser la fonction  success et  error:
  1. La fonction success & error avec un paramètre
  2. ​​​​​​​La fonction success & error avec 4 paramètres
  • 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
        }
    );

View more Tutorials: