In this tutorial we will see the integration of AngularJS with Spring MVC. We are going to create CRUD application using Spring REST API on server side and AngularJS is encapsulated within JSP file on client side. Thus the client will communicate with server asynchronously using AngularJS $http service.
If you need PHP version https://roytuts.com/angularjs-php-rest-crud-example/
If you need Codeigniter version https://roytuts.com/angularjs-codeigniter-rest-crud-example/
Prerequisites
Eclipse Neon
Maven 3.3.9
Java 1.8
Spring 4.3.8.RELEASE
Jackson 2.5.3
AngularJS 1.6.4
The name of the project in Eclipse is angularjs-spring.
Project dependencies in 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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>roytuts</groupId> <artifactId>angularjs-spring</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>angularjs-spring Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <jackson.version>2.5.3</jackson.version> <spring.version>4.3.8.RELEASE</spring.version> </properties> <dependencies> <!-- Spring mvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- jackson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <!-- Servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!-- jstl --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <build> <finalName>angularjs-spring</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warSourceDirectory>src/main/webapp</warSourceDirectory> <warName>angularjs-spring</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>
Server Side
We are going to see everything on annotations based so create first configuration classes.
Create below Spring configuration class that will provide facilities for view resolver, resource handler, annotation based MVC etc.
package com.roytuts.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; @EnableWebMvc @Configuration @ComponentScan(basePackages = "com.roytuts") public class WebMvcConfigurer extends WebMvcConfigurerAdapter { @Override public void configureViewResolvers(ViewResolverRegistry registry) { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); registry.viewResolver(viewResolver); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/assets/**").addResourceLocations("/assets/"); } }
Create below Spring initializer class that helps us to load Dispatcher Servlet and other Spring configurations.
package com.roytuts.config; import javax.servlet.Filter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import com.roytuts.filter.SameOriginPolicyFilter; public class WebMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { WebMvcConfigurer.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } @Override protected Filter[] getServletFilters() { Filter[] singleton = { new SameOriginPolicyFilter() }; return singleton; } }
Create below filter to handle issues related to the Same Origin Policy(SOP). SOP is an important security concept in browsers. SOP allows client-side programming languages, such as JavaScript, only access to resources in the same domain. SOP is very important for internet applications, because you want to prevent, that everybody can access to your services and content. For more information on SOP please refer to the link https://en.wikipedia.org/wiki/Same-origin_policy and https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
package com.roytuts.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; public class SameOriginPolicyFilter implements Filter { @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) resp; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "X-requested-with, Content-Type"); chain.doFilter(req, resp); } @Override public void destroy() { } }
Create below DTO class
package com.roytuts.dto; public class Product { private int id; private String name; private double price; public Product() { } public Product(int id, String name, double price) { this.id = id; this.name = name; this.price = price; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (id ^ (id >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Product other = (Product) obj; if (id != other.id) return false; return true; } }
Create below Spring based service to handle user related activities.
package com.roytuts.service; import java.util.List; import com.roytuts.dto.Product; public interface ProductService { Product findProductById(int id); Product findProductByName(String name); void saveProduct(Product product); void updateProduct(Product product); void deleteProductById(int id); List<Product> findAllProducts(); void deleteAllProducts(); public boolean isProductAvailable(Product product); }
package com.roytuts.service; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.springframework.stereotype.Service; import com.roytuts.dto.Product; @Service public class ProductServiceImpl implements ProductService { private static AtomicInteger counter = new AtomicInteger(); private static List<Product> products; static { products = populateProducts(); } @Override public Product findProductById(int id) { for (Product product : products) { if (id == product.getId()) { return product; } } return null; } @Override public Product findProductByName(String name) { for (Product product : products) { if (name.equalsIgnoreCase(product.getName())) { return product; } } return null; } @Override public void saveProduct(Product product) { product.setId(counter.incrementAndGet()); products.add(product); } @Override public void updateProduct(Product product) { int index = products.indexOf(product); products.set(index, product); } @Override public void deleteProductById(int id) { Iterator<Product> it = products.iterator(); while (it.hasNext()) { Product product = it.next(); if (id == product.getId()) { it.remove(); } } } @Override public List<Product> findAllProducts() { return products; } @Override public void deleteAllProducts() { products.clear(); } @Override public boolean isProductAvailable(Product product) { return findProductById(product.getId()) != null || findProductByName(product.getName()) != null; } private static List<Product> populateProducts() { List<Product> products = new ArrayList<Product>(); products.add(new Product(counter.incrementAndGet(), "Mobile", 25498.00)); products.add(new Product(counter.incrementAndGet(), "Desktop", 32658.00)); products.add(new Product(counter.incrementAndGet(), "Laptop", 52147.00)); products.add(new Product(counter.incrementAndGet(), "Tab", 18254.00)); return products; } }
Create below Spring REST service class to handle user request/response.
package com.roytuts.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; 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.RestController; import org.springframework.web.util.UriComponentsBuilder; import com.roytuts.dto.Product; import com.roytuts.service.ProductService; @RestController public class ProductController { @Autowired private ProductService productService; @RequestMapping(value = "/products", method = RequestMethod.GET) public ResponseEntity<List<Product>> getProducts() { List<Product> products = productService.findAllProducts(); if (products.isEmpty()) { return new ResponseEntity<List<Product>>(HttpStatus.NO_CONTENT); } return new ResponseEntity<List<Product>>(products, HttpStatus.OK); } @RequestMapping(value = "/product", method = RequestMethod.POST) public ResponseEntity<Void> saveProduct(@RequestBody Product product, UriComponentsBuilder ucBuilder) { if (product == null || product.getName() == null || "".equals(product.getName())) { throw new RuntimeException("Product Name and Price are required fields"); } if (productService.isProductAvailable(product)) { System.out.println("A Product with name " + product.getName() + " already exist"); return new ResponseEntity<Void>(HttpStatus.CONFLICT); } productService.saveProduct(product); HttpHeaders headers = new HttpHeaders(); headers.setLocation(ucBuilder.path("/product/{id}").buildAndExpand(product.getId()).toUri()); return new ResponseEntity<Void>(headers, HttpStatus.CREATED); } @RequestMapping(value = "/product", method = RequestMethod.PUT) public ResponseEntity<Void> updateProduct(@RequestBody Product product) { if (product == null || product.getName() == null || "".equals(product.getName()) || product.getId() <= 0) { throw new RuntimeException("Product Name, Id, Price are required fields"); } Product currentProduct = productService.findProductById(product.getId()); if (currentProduct == null) { return new ResponseEntity<Void>(HttpStatus.NOT_FOUND); } productService.updateProduct(product); return new ResponseEntity<Void>(HttpStatus.OK); } @RequestMapping(value = "/product/{id}", method = RequestMethod.GET) public ResponseEntity<Product> getProductByid(@PathVariable int id) { Product product = productService.findProductById(id); if (product == null) { return new ResponseEntity<Product>(HttpStatus.NOT_FOUND); } return new ResponseEntity<Product>(product, HttpStatus.OK); } @RequestMapping(value = "/product/{name}", method = RequestMethod.GET) public ResponseEntity<Product> getProductByName(@PathVariable String name) { Product product = productService.findProductByName(name); if (product == null) { return new ResponseEntity<Product>(HttpStatus.NOT_FOUND); } return new ResponseEntity<Product>(product, HttpStatus.OK); } @RequestMapping(value = "/product/{id}", method = RequestMethod.DELETE) public ResponseEntity<Void> deleteProductByid(@PathVariable int id) { Product currentProduct = productService.findProductById(id); if (currentProduct == null) { return new ResponseEntity<Void>(HttpStatus.NOT_FOUND); } productService.deleteProductById(id); return new ResponseEntity<Void>(HttpStatus.OK); } @RequestMapping(value = "/products", method = RequestMethod.DELETE) public ResponseEntity<Void> deleteProducts() { productService.deleteAllProducts(); return new ResponseEntity<Void>(HttpStatus.OK); } }
Create Spring main controller class to handle user’s home page request.
package com.roytuts.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/") public class MainController { @RequestMapping(method = RequestMethod.GET) public String getIndexPage() { return "index"; } }
Client Side
Client side of the Application is AngularJS based. For more information on Angular JS please refer to the link https://docs.angularjs.org/tutorial
AngularJS module
The angular.module is a global place for creating, registering and retrieving AngularJS modules. A module is a collection of services, directives, controllers, filters, and configuration information. angular.module is used to configure the $injector.
A module can be used by AngularJS to bootstrap an application. AngularJS loads a module by passing the module name to ng-app directive and it is the main entry point for the application.
Create a new module using the following code snippets/assets/js/app.js
'use strict'; var app = angular.module('myCrudApp', []);
'use strict'
does the following
- It catches some common coding bloopers, throwing exceptions.
- It prevents, or throws errors, when relatively “unsafe” actions are taken (such as gaining access to the global object).
- It disables features that are confusing or poorly thought out.
AngularJS Service to communicate with Server/assets/js/productService.js
'use strict'; angular.module('myCrudApp').factory('ProductService', ['$http', '$q', function($http, $q){ var REST_SERVICE_URI = 'http://localhost:8080/angularjs-spring/'; var factory = { findAllProducts: findAllProducts, createProduct: createProduct, updateProduct: updateProduct, deleteProduct: deleteProduct }; return factory; function findAllProducts() { var deferred = $q.defer(); $http.get(REST_SERVICE_URI+'products') .then( function (response) { deferred.resolve(response.data); }, function(errResponse){ console.error('Error while fetching products'); deferred.reject(errResponse); } ); return deferred.promise; } function createProduct(product) { var deferred = $q.defer(); $http.post(REST_SERVICE_URI+'product', product) .then( function (response) { deferred.resolve(response.data); }, function(errResponse){ console.error('Error while creating product'); deferred.reject(errResponse); } ); return deferred.promise; } function updateProduct(product) { var deferred = $q.defer(); $http.put(REST_SERVICE_URI+'product', product) .then( function (response) { deferred.resolve(response.data); }, function(errResponse){ console.error('Error while updating product'); deferred.reject(errResponse); } ); return deferred.promise; } function deleteProduct(id) { var deferred = $q.defer(); $http.delete(REST_SERVICE_URI+'product/'+id) .then( function (response) { deferred.resolve(response.data); }, function(errResponse){ console.error('Error while deleting product'); deferred.reject(errResponse); } ); return deferred.promise; } }]);
Note: do not forget to change the value of REST_SERVICE_URI
In AngularJS based applications, the preferred way to communicate with server is using AngularJS built-in $http Service. AngularJS $http service allows us to communicate with server endpoints using XHR [browser’s XMLHttpRequest Object] API. The $http API is based on the deferred/promise APIs exposed by the $q service(https://docs.angularjs.org/api/ng/service/$q) which is an implementation of Promise interface, which is a standardized way of dealing with asynchronous calls in JavaScript.
AngularJS Controller to handle user’s request/response/assets/js/productController.js
'use strict'; angular.module('myCrudApp').controller('ProductController', ['$scope', 'ProductService', function($scope, ProductService) { var self = this; self.product = { id: null, name:'', price: 0.0 }; self.products = []; self.submit = submit; self.edit = edit; self.remove = remove; self.reset = reset; findAllProducts(); function findAllProducts(){ ProductService.findAllProducts() .then( function(d) { self.products = d; }, function(errResponse){ console.error('Error while fetching products'); } ); } function createProduct(product){ ProductService.createProduct(product) .then( findAllProducts, function(errResponse){ console.error('Error while creating product'); } ); } function updateProduct(product){ ProductService.updateProduct(product) .then( findAllProducts, function(errResponse){ console.error('Error while updating product'); } ); } function deleteProduct(id){ ProductService.deleteProduct(id) .then( findAllProducts, function(errResponse){ console.error('Error while deleting product'); } ); } function submit() { if(self.product.id===null){ console.log('Saving New Product', self.product); createProduct(self.product); }else{ updateProduct(self.product); console.log('Product updated with id ', self.product.id); } reset(); } function edit(id){ console.log('id to be edited', id); for(var i = 0; i < self.products.length; i++){ if(self.products[i].id === id) { self.product = angular.copy(self.products[i]); break; } } } function remove(id){ console.log('id to be deleted', id); if(self.product.id === id) {//clean form if the product to be deleted is shown there. reset(); } deleteProduct(id); } function reset(){ self.product={id:null, name:'', price:0.0}; $scope.myForm.$setPristine(); //reset Form } }]);
Controllers are considered as the driver for Model and View changes. They are the gateway between the model (the data in our application), and the view (whatever a user sees on screen and interacts with). The core responsibilities includes, providing data to UI, managing presentation logic, such as which element to show/hide, what style to apply on them, etc, handling user input, such as what happens when a user clicks something or how a text input should be validated, processing data from user input to be sent to server etc.
View for Spring MVC application/WEB-INF/views/index.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <title>AngularJS Spring MVC Example</title> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"> <link href="<c:url value='/assets/css/app.css' />" rel="stylesheet"></link> </head> <body ng-app="myCrudApp" class="ng-cloak"> <div class="generic-container" ng-controller="ProductController as ctrl"> <div class="panel panel-default"> <div class="panel-heading"><span class="lead">Create new product</span></div> <div class="formcontainer"> <form ng-submit="ctrl.submit()" name="myForm" class="form-horizontal"> <input type="hidden" ng-model="ctrl.product.id" /> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-2 control-lable" for="name">Name</label> <div class="col-md-7"> <input type="text" ng-model="ctrl.product.name" id="name" class="field form-control input-sm" placeholder="Enter product name" required ng-minlength="3"/> <div class="has-error" ng-show="myForm.$dirty"> <span ng-show="myForm.name.$error.required">This is a required field</span> <span ng-show="myForm.name.$error.minlength">Minimum length required is 3</span> <span ng-show="myForm.name.$invalid">This field is invalid </span> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-2 control-lable" for="price">Price</label> <div class="col-md-7"> <input type="number" ng-model="ctrl.product.price" id="price" min="1" class="field form-control input-sm" placeholder="Enter product price"/> <div class="has-error" ng-show="myForm.$dirty"> <span ng-show="myForm.price.$error.required">This is a required field</span> <span ng-show="myForm.price.$error.number">Not a valid number</span> </div> </div> </div> </div> <div class="row"> <div class="form-actions floatRight"> <input type="submit" value="{{!ctrl.product.id ? 'Add' : 'Update'}}" class="btn btn-primary btn-sm" ng-disabled="myForm.$invalid"> <button type="button" ng-click="ctrl.reset()" class="btn btn-warning btn-sm" ng-disabled="myForm.$pristine">Reset Form</button> </div> </div> </form> </div> </div> <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"><span class="lead">List of Products</span></div> <div class="tablecontainer"> <table class="table table-hover"> <thead> <tr> <th>ID</th> <th>Name</th> <th>Price</th> <th width="25%"></th> </tr> </thead> <tbody> <tr ng-repeat="p in ctrl.products"> <td><span ng-bind="p.id"></span></td> <td><span ng-bind="p.name"></span></td> <td><span ng-bind="p.price"></span></td> <td> <button type="button" ng-click="ctrl.edit(p.id)" class="btn btn-success custom-width">Edit</button> <button type="button" ng-click="ctrl.remove(p.id)" class="btn btn-danger custom-width">Remove</button> </td> </tr> </tbody> </table> </div> </div> </div> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script> <script src="<c:url value='/assets/js/app.js' />"></script> <script src="<c:url value='/assets/js/productService.js' />"></script> <script src="<c:url value='/assets/js/productController.js' />"></script> </body> </html>
Here in the above view file bootstrap framework along with AngularJS has been included in order to enhance the look & feel of the application./assets/css/app.css
body, #mainWrapper { height: 70%; background-color: rgb(245, 245, 245); } body, .form-control { font-size: 12px !important; } .floatRight { float: right; margin-right: 18px; } .has-error { color: red; } .formcontainer { background-color: #DAE8E8; padding: 20px; } .tablecontainer { padding-left: 20px; } .generic-container { width: 97%; margin-left: 20px; margin-top: 20px; margin-bottom: 20px; padding: 20px; background-color: #EAE7E7; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 0 30px black; } .custom-width { width: 80px !important; } .field.ng-valid { background-color: lightgreen; } .field.ng-dirty.ng-invalid-required { background-color: red; } .field.ng-dirty.ng-invalid-minlength { background-color: yellow; }
Hit the URL http://localhost:8080/angularjs-spring/
in the browser to play with the example.
Source Code
Thanks for reading.
Excelente