This post shows integrating AngularJS with Spring MVC 4, focusing on routing using ui.router module. We will discuss ui.router based route configuration using states, $stateProvider, $urlRouterProvider, accessing route parameters using $stateParams, discuss route configuration details with template, templateUrl, controller & resolve. Let’s get going.
In our application, Client side is based on AngularJS ui.router, demonstrating route configuration using states. Server side is based on Spring , serving HTML using velocity templates and data using Spring REST API. This is what our final Application will look like:
To learn about AngularJS Routing using UI-Router in general, you can refer to AngularJS Routing Tutorial using UI-Router.
Let’s get started.
Following technologies being used:
<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>com.websystique.springmvc</groupId> <artifactId>Spring4MVCAngularJSRoutingWithUIRouterExample</artifactId> <packaging>war</packaging> <version>1.0.0</version> <name>Spring4MVCAngularJSRoutingWithUIRouterExample Maven Webapp</name> <properties> <springframework.version>4.2.0.RELEASE</springframework.version> <jackson.version>2.5.3</jackson.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</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>Spring4MVCAngularJSRoutingWithUIRouterExample</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </pluginManagement> <finalName>Spring4MVCAngularJSRoutingWithUIRouterExample</finalName> </build> </project>
Client side of our Application is AngularJS based. If you would like to add AngularJs in your skill set, Detailed AngularJS Tutorial is available for you to deep dive in one of the most popular Javascript Meta framwework.
Configure routes using AngularUI ui.router module. AngularUI Router is a routing framework for AngularJS, which allows you to organize the parts of your interface into a state machine. Unlike the $route service in the Angular ngRoute module, which is organized around URL routes, UI-Router is organized around states, which may optionally have routes, as well as other behavior attached.
Route configuration is done in AngularJS module’s config function, using $stateProvider service.
app.js
'use strict'; var App = angular.module('myApp',['ui.router']); App.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider){ $urlRouterProvider.otherwise("/category") $stateProvider .state('category', { url: "/category", templateUrl: 'category', controller : "CategoryController as ctgController", resolve: { async: ['ItemService', function(ItemService) { return ItemService.fetchCategoryList(); }] } }) .state('category.list', { url: '/{categoryId:[A-Za-z]{0,9}}', templateUrl: function(params){ return 'category/' + params.categoryId; }, controller : "ItemListController as itemListCtrl", resolve: { async: ['ItemService', '$stateParams', function(ItemService, $stateParams) { return ItemService.fetchAllItems($stateParams.categoryId); }] } }) .state('category.list.detail', { url: '/{itemId:[0-9]{1,9}}', templateUrl: function(params){ return 'category/' + params.categoryId +'/'+params.itemId; }, controller : "ItemDetailsController as itemDetailsCtrl", resolve: { async: ['ItemService', '$stateParams', function(ItemService, $stateParams) { return ItemService.fetchSpecificItem($stateParams.categoryId, $stateParams.itemId); }] } }) }]);
Our example demonstrates Nested States using ui.router. In this example, we have one top-level state “category” and two nested states “category.list” and “category.list.detail”. Note that parent state of “category.list” is “category” [look at dot in the name]. Similarly, “category.list” is the parent state of “category.list.detail”.
Main Highlights:
For more in depth details, please refer to Angularjs Routing Tutorial using ui-router.
Configure Service which will eventually communicate with our Spring based server. For any doubts on service setup, please refer to AngularJS Services Tutorial.
ItemService.js
'use strict'; App.factory('ItemService', ['$http', '$q', function($http, $q){ return { fetchCategoryList: function() {//Fetches category list from server. return $http.get('http://localhost:8080/Spring4MVCAngularJSRoutingWithUIRouterExample/categories') .then( function(response){ return response.data; }, function(errResponse){ console.error('Error while fetching Items'); return $q.reject(errResponse); } ); }, fetchAllItems: function(category) {//Fetches list of item for a specific category. return $http.get('http://localhost:8080/Spring4MVCAngularJSRoutingWithUIRouterExample/item/'+category) .then( function(response){ return response.data; }, function(errResponse){ console.error('Error while fetching Items'); return $q.reject(errResponse); } ); }, fetchSpecificItem: function(category,id) {//Fetches a specific item based on category and item id. return $http.get('http://localhost:8080/Spring4MVCAngularJSRoutingWithUIRouterExample/item/'+category+'/'+id) .then( function(response){ return response.data; }, function(errResponse){ console.error('Error while fetching specific Item'); return $q.reject(errResponse); } ); } }; }]);
Create controller for Item lists and details.
CategoryController.js
: Used to display list of categories [on the home page].
'use strict'; App.controller('CategoryController', ['async', function(async) { var self = this; self.categories=async; }]);
ItemListController.js
: Used to display list of items.
'use strict'; App.controller('ItemListController', ['async', function(async) { var self = this; self.items=async; }]);
ItemDetailsController.js
: Used to display details of a specific item.
'use strict'; App.controller('ItemDetailsController', ['async', function(async) { var self = this; self.item=async; }]);
In all above controller, notice how we are passing the Promise ‘async’ from service into controller. For any doubts on controller setup, please refer to AngularJS Controllers Tutorial.
Shown below is the main page, containing only ui-viw
directive.
index.html
<!doctype html> <html lang="en" ng-app="myApp"> <head> <meta charset="utf-8"> <title>Service App</title> <link rel="stylesheet" href="resources/css/bootstrap.css" /> <link rel="stylesheet" href="resources/css/app.css" /> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js"></script> <script src="http://angular-ui.github.io/ui-router/release/angular-ui-router.js"></script> <script src="resources/js/app.js"></script> <script src="resources/js/service/ItemService.js"></script> <script src="resources/js/controller/CategoryController.js"></script> <script src="resources/js/controller/ItemListController.js"></script> <script src="resources/js/controller/ItemDetailsController.js"></script> </head> <body> <div ui-view></div> </body> </html>
On application startup, config function will be invoked which will eventually set the default state to ‘category’ [thanks to $urlRouterProvider.otherwise(“/category”) ], and will activate the category state. Below is the peace of code [from app.js] responsible for this:
$urlRouterProvider.otherwise("/category") $stateProvider .state('category', { url: "/category", templateUrl: 'category', controller : "CategoryController as ctgController", resolve: { async: ['ItemService', function(ItemService) { return ItemService.fetchCategoryList(); }] } })
resolve will be called on this default state, which will eventually send a request to server [using service] to get the category-list. The promise from service [async] will be passed to controller which will be applied on templates of this state.
Template for Category state is shown below:
all_categories.html
<div class="generic-container"> <h2>SellPoint - AngularJS UI-Router based Routing Application</h2> <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"><span class="lead">All Categories</span></div> <div class="tablecontainer"> <ul> <li ng-repeat="category in ctgController.categories">{{item}} <a ui-sref="category.list({categoryId:category})">{{category}}</a> </li> </ul> </div> </div> <div ui-view></div> </div>
Main Highlights:
ui-sref
instead of regular href. ui-sref is a directive which in addition to auto-generating href attribute, binds a link [<a> tag] to a state. Clicking the link will trigger a state transition with optional parameters. In above excerpt, we have said that clicking the link will trigger “category.list” state. Nested State
. In this case we say the category.list is a nested state of “category” state. $stateParams
, and used to perform further logic.category_computers.html
<div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"><span class="lead">List of Computers</span></div> <div class="tablecontainer"> <table class="table table-hover"> <thead> <tr> <th>ID</th> <th>Brand</th> <th>Model</th> <th>Price (EUR)</th> </tr> </thead> <tbody> <tr ng-repeat="item in itemListCtrl.items"> <td>{{item.id}}</td> <td>{{item.brand}}</td> <td><a ui-sref="category.list.detail({categoryId:'Computers',itemId:item.id})">{{item.model}}</a></td> <td>{{item.price}}</td> </tr> </tbody> </table> </div> </div> <div ui-view></div>
Main Highlights:
To recall, following is the excerpt from app.js related to this state:
.state('category.list', { url: '/{categoryId:[A-Za-z]{0,9}}', templateUrl: function(params){ return 'category/' + params.categoryId; }, controller : "ItemListController as itemListCtrl", resolve: { async: ['ItemService', '$stateParams', function(ItemService, $stateParams) { return ItemService.fetchAllItems($stateParams.categoryId); }] } })
As we saw before, the categoryId parameter is passed from category state [from within all_categories.html]. We access this parameter here using $stateParams and send that parameter’s value to server to fetch the list of items for that specific category. Then we pass the Promise of that request [‘async’] to controller which eventually use it to show details on template for list page.
Additionally, look at how we have used the state parameter to create specific request URL to send to server to fetch specific template. AngularJS let’s us do that by passing $stateParams as a parameter in a function within which we can access it to do eventually use the parameters. Nice.
category_computers_detail.html
<div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"><span class="lead">Computer Details</span></div> <div class="tablecontainer"> <table class="table table-hover"> <tr> <th>ID</th><td>{{itemDetailsCtrl.item.id}}</td><th>Brand</th><td>{{itemDetailsCtrl.item.brand}}</td> </tr> <tr> <th>Model</th><td>{{itemDetailsCtrl.item.model}}</td><th>State</th><td>{{itemDetailsCtrl.item.state}}</td> </tr> <tr> <th>Price (EUR)</th><td>{{itemDetailsCtrl.item.price}}</td><th>Processor (GHZ)</th><td>{{itemDetailsCtrl.item.processorSpeed}}</td> </tr> <tr> <th>RAM (GB)</th><td>{{itemDetailsCtrl.item.ram}}</td><th>Disk Capacity</th><td>{{itemDetailsCtrl.item.diskCapacity}}</td> </tr> </table> </div> </div>
This is the template for ‘category.list.detail’ state which is the child state of ‘category.list’. That means it will be injected in ui-view of it’s parent template which is category_computers.html. To recall, following is the excerpt from app.js related to this state:
.state('category.list.detail', { url: '/{itemId:[0-9]{1,9}}', templateUrl: function(params){ return 'category/' + params.categoryId +'/'+params.itemId; }, controller : "ItemDetailsController as itemDetailsCtrl", resolve: { async: ['ItemService', '$stateParams', function(ItemService, $stateParams) { return ItemService.fetchSpecificItem($stateParams.categoryId, $stateParams.itemId); }] } })
We are accessing the parameters passed from parent state using $stateParams, and then sending there values with request to server to get the detail of a specific item which belongs to a specific category.
That’s all for client side. To complete, shown below are remaining templates used in this application.
category_phones.html
<div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"><span class="lead">List of Phones</span></div> <div class="tablecontainer"> <table class="table table-hover"> <thead> <tr> <th>ID</th> <th>Brand</th> <th>Model</th> <th>Price (EUR)</th> </tr> </thead> <tbody> <tr ng-repeat="item in itemListCtrl.items"> <td>{{item.id}}</td> <td>{{item.brand}}</td> <td><a ui-sref="category.list.detail({categoryId:'Phones',itemId:item.id})">{{item.model}}</a></td> <td>{{item.price}}</td> </tr> </tbody> </table> </div> </div> <div ui-view></div>
category_phones_detail.html
<div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"><span class="lead">Phone Details</span></div> <div class="tablecontainer"> <table class="table table-hover"> <tr> <th>ID</th><td>{{itemDetailsCtrl.item.id}}</td><th>Brand</th><td>{{itemDetailsCtrl.item.brand}}</td> </tr> <tr> <th>Model</th><td>{{itemDetailsCtrl.item.model}}</td><th>State</th><td>{{itemDetailsCtrl.item.state}}</td> </tr> <tr> <th>Price (EUR)</th><td>{{itemDetailsCtrl.item.price}}</td><th>Size</th><td>{{itemDetailsCtrl.item.size}}</td> </tr> <tr> <th>Camera (MB)</th><td>{{itemDetailsCtrl.item.camera}}</td><th>SD Card (GB)</th><td>{{itemDetailsCtrl.item.sdCard}}</td> </tr> <tr> <th>Replaceable Battery</th><td>{{itemDetailsCtrl.item.replacableBattery}}</td><th></th><td></td> </tr> </table> </div> </div>
category_printers.html
<div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"><span class="lead">List of Printers</span></div> <div class="tablecontainer"> <table class="table table-hover"> <thead> <tr> <th>ID</th> <th>Brand</th> <th>Model</th> <th>Price (EUR)</th> </tr> </thead> <tbody> <tr ng-repeat="item in itemListCtrl.items"> <td>{{item.id}}</td> <td>{{item.brand}}</td> <td><a ui-sref="category.list.detail({categoryId:'Printers',itemId:item.id})">{{item.model}}</a></td> <td>{{item.price}}</td> </tr> </tbody> </table> </div> </div> <div ui-view></div>
category_printers_detail.html
<div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"><span class="lead">Printer Details</span></div> <div class="tablecontainer"> <table class="table table-hover"> <tr> <th>ID</th><td>{{itemDetailsCtrl.item.id}}</td><th>Brand</th><td>{{itemDetailsCtrl.item.brand}}</td> </tr> <tr> <th>Model</th><td>{{itemDetailsCtrl.item.model}}</td><th>State</th><td>{{itemDetailsCtrl.item.state}}</td> </tr> <tr> <th>Price (EUR)</th><td>{{itemDetailsCtrl.item.price}}</td><th>Technology</th><td>{{itemDetailsCtrl.item.technologie}}</td> </tr> <tr> <th>Paper Format</th><td>{{itemDetailsCtrl.item.paperFormat}}</td><th>PPM</th><td>{{itemDetailsCtrl.item.pagesPerMinute}}</td> </tr> </table> </div> </div>
Spring Configuration Class:
HelloWorldConfiguration.java
package com.websystique.springmvc.configuration; import org.springframework.context.annotation.Bean; 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.velocity.VelocityConfigurer; import org.springframework.web.servlet.view.velocity.VelocityView; import org.springframework.web.servlet.view.velocity.VelocityViewResolver; @Configuration @EnableWebMvc @ComponentScan(basePackages = "com.websystique.springmvc") public class HelloWorldConfiguration extends WebMvcConfigurerAdapter{ @Bean public VelocityConfigurer velocityConfig() { VelocityConfigurer velocityConfigurer = new VelocityConfigurer(); velocityConfigurer.setResourceLoaderPath("/WEB-INF/velocity/"); return velocityConfigurer; } @Override public void configureViewResolvers(ViewResolverRegistry registry) { VelocityViewResolver viewResolver = new VelocityViewResolver(); viewResolver.setViewClass(VelocityView.class); viewResolver.setCache(true); viewResolver.setPrefix(""); viewResolver.setSuffix(".html"); viewResolver.setExposeSpringMacroHelpers(true); registry.viewResolver(viewResolver); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); } }
Create WelcomePage controller
IndexController.java
package com.websystique.springmvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/") public class IndexController {//Serves main index.html @RequestMapping(method = RequestMethod.GET) public String getIndexPage() { return "index"; } }
Create Template Controller handling the requests for templates:
TemplateController.java
package com.websystique.springmvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class TemplateController {//Serves Templates. @RequestMapping(value="/category") public String getMainTemplate() { return "template/all_categories"; } @RequestMapping(value="/category/Computers") public String getComputersTemplate() { return "template/category_computers"; } @RequestMapping(value="/category/Phones") public String getPhonesTemplate() { return "template/category_phones"; } @RequestMapping(value="/category/Printers") public String getPrintersTemplate() { return "template/category_printers"; } @RequestMapping(value="/category/Computers/{id}") public String getComputersDetailTemplate(@PathVariable("id") long id) { return "template/category_computers_detail"; } @RequestMapping(value="/category/Phones/{id}") public String getPhonesDetailTemplate(@PathVariable("id") long id) { return "template/category_phones_detail"; } @RequestMapping(value="/category/Printers/{id}") public String getPrintersDetailTemplate(@PathVariable("id") long id) { return "template/category_printers_detail"; } }
Create REST Controller handling the requests for list and detail of items:
ItemController.java
package com.websystique.springmvc.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import com.websystique.springmvc.service.ItemService; @Controller public class ItemController {//Serves Data. @Autowired ItemService itemService; //Service which will do all data retrieval/manipulation work @RequestMapping("/categories") public ResponseEntity<List> listAllCategories() { System.out.println("*************************************ListAllItems"); List<String> categories = itemService.populateAllCategories(); if(categories.isEmpty()){ return new ResponseEntity<List>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND } return new ResponseEntity<List>(categories, HttpStatus.OK); } @RequestMapping(value="/item/Computers") public ResponseEntity<List> listAllComputers() { System.out.println("*************************************ListAllComputers"); List items = itemService.findItemsByCategory("computers"); if(items.isEmpty()){ return new ResponseEntity<List>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND } return new ResponseEntity<List>(items, HttpStatus.OK); } @RequestMapping(value="/item/Computers/{id}") public ResponseEntity<Object> findSpecificComputer(@PathVariable("id") long id) { System.out.println("*************************************findSpecificComputer"); Object item = itemService.findItemById(id, "computers"); if(item == null){ return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND } return new ResponseEntity<Object>(item, HttpStatus.OK); } @RequestMapping(value="/item/Phones") public ResponseEntity<List> listAllPhones() { System.out.println("*************************************ListAllPhones"); List items = itemService.findItemsByCategory("phones"); if(items.isEmpty()){ return new ResponseEntity<List>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND } return new ResponseEntity<List>(items, HttpStatus.OK); } @RequestMapping(value="/item/Phones/{id}") public ResponseEntity<Object> findSpecificPhone(@PathVariable("id") long id) { System.out.println("*************************************findSpecificPhone"); Object item = itemService.findItemById(id, "phones"); if(item == null){ return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND } return new ResponseEntity<Object>(item, HttpStatus.OK); } @RequestMapping(value="/item/Printers") public ResponseEntity<List> listAllPrinters() { System.out.println("*************************************ListAllPrinters"); List items = itemService.findItemsByCategory("printers"); if(items.isEmpty()){ return new ResponseEntity<List>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND } return new ResponseEntity<List>(items, HttpStatus.OK); } @RequestMapping(value="/item/Printers/{id}") public ResponseEntity<Object> findSpecificPrinter(@PathVariable("id") long id) { System.out.println("*************************************findSpecificPrinter"); Object item = itemService.findItemById(id, "printers"); if(item == null){ return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND } return new ResponseEntity<Object>(item, HttpStatus.OK); } }
Services for providing dummy data for item lists and details:
ItemService.java
package com.websystique.springmvc.service; import java.util.List; public interface ItemService { List findItemsByCategory(String category); Object findItemById(long id, String category); List<String> populateAllCategories(); }
ItemServiceImpl.java
package com.websystique.springmvc.service; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import com.websystique.springmvc.model.Computer; import com.websystique.springmvc.model.Phone; import com.websystique.springmvc.model.Printer; import com.websystique.springmvc.model.State; @Service("itemService") public class ItemServiceImpl implements ItemService{ private static final AtomicLong counter = new AtomicLong(); private static List<Computer> computers; private static List<Phone> phones; private static List<Printer> printers; static{ computers= populateDummyComputers(); phones= populateDummyPhones(); printers= populateDummyPrinters(); } public List findItemsByCategory(String category) { if(StringUtils.equalsIgnoreCase(category, "computers")){ return computers; } else if(StringUtils.equalsIgnoreCase(category, "phones")){ return phones; } else if(StringUtils.equalsIgnoreCase(category, "printers")){ return printers; } return computers; } public Object findItemById(long id, String category) { if(category.equalsIgnoreCase("computers")){ for(Computer computer : computers){ if(computer.getId() == id){ return computer; } } }else if(category.equalsIgnoreCase("phones")){ for(Phone phone : phones){ if(phone.getId() == id){ return phone; } } } if(category.equalsIgnoreCase("printers")){ for(Printer printer : printers){ if(printer.getId() == id){ return printer; } } } return null; } public List<String> populateAllCategories(){ List<String> allCategories = new ArrayList<String>(); allCategories.add("Computers"); allCategories.add("Phones"); allCategories.add("Printers"); return allCategories; } private static List<Computer> populateDummyComputers(){ List<Computer> computers = new ArrayList<Computer>(); computers.add(new Computer(counter.incrementAndGet(),"Lenovo", "T420", State.NEW, 500, 2.4, 8, 1000)); computers.add(new Computer(counter.incrementAndGet(),"Lenovo", "T640", State.NEW, 2000, 3.6, 32, 2000)); computers.add(new Computer(counter.incrementAndGet(),"Apple", "IMAC21.5", State.NEW, 1400, 2.6, 8, 1000)); computers.add(new Computer(counter.incrementAndGet(),"HP", "Pavilion", State.NEW, 900, 2.4, 8, 1000)); computers.add(new Computer(counter.incrementAndGet(),"Dell", "E6510", State.NEW, 500, 2.3, 8, 500)); return computers; } private static List<Phone> populateDummyPhones(){ List<Phone> phones = new ArrayList<Phone>(); phones.add(new Phone(counter.incrementAndGet(),"Apple", "IPhone6s", State.NEW, 800, 5.2, false,false,15)); phones.add(new Phone(counter.incrementAndGet(),"Huwawi", "Nexus6P", State.NEW, 700, 5.7, false,false,13)); phones.add(new Phone(counter.incrementAndGet(),"Samsung", "Note5", State.NEW, 600, 5.7, false,false,14)); phones.add(new Phone(counter.incrementAndGet(),"HTC", "M9", State.NEW, 580, 5.5, true,true,16)); phones.add(new Phone(counter.incrementAndGet(),"LG", "G4", State.NEW, 550, 5.7, true,true,15)); return phones; } private static List<Printer> populateDummyPrinters(){ List<Printer> printers = new ArrayList<Printer>(); printers.add(new Printer(counter.incrementAndGet(),"HP", "OfficeJet 7500A", State.NEW, 200, "A4", "InkJet",15)); printers.add(new Printer(counter.incrementAndGet(),"Brother", "J6520dw", State.NEW, 180, "A4", "InkJet",12)); printers.add(new Printer(counter.incrementAndGet(),"EPSON", "XP-820", State.NEW, 210, "A4", "InkJet",14)); return printers; } }
Model Classes:
Computer.java
package com.websystique.springmvc.model; public class Computer{ private long id; private String brand; private String model; private State state; private double price; private double processorSpeed; private int ram; private int diskCapacity; public Computer(long id,String brand, String model, State state, double price, double processorSpeed, int ram, int diskCapacity){ this.id = id; this.brand = brand; this.model = model; this.state = state; this.price = price; this.processorSpeed = processorSpeed; this.ram =ram; this.diskCapacity = diskCapacity; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getModel() { return model; } public void setModel(String model) { this.model = model; } public State getState() { return state; } public void setState(State state) { this.state = state; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public double getProcessorSpeed() { return processorSpeed; } public void setProcessorSpeed(double processorSpeed) { this.processorSpeed = processorSpeed; } public int getRam() { return ram; } public void setRam(int ram) { this.ram = ram; } public int getDiskCapacity() { return diskCapacity; } public void setDiskCapacity(int diskCapacity) { this.diskCapacity = diskCapacity; } @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; Computer other = (Computer) obj; if (id != other.id) return false; return true; } @Override public String toString() { return "Computer [id=" + id + ", brand=" + brand + ", model=" + model + ", state=" + state + ", price=" + price + ", processorSpeed=" + processorSpeed + ", ram=" + ram + ", diskCapacity=" + diskCapacity + "]"; } }
Phone.java
package com.websystique.springmvc.model; public class Phone{ private long id; private String brand; private String model; private State state; private double price; private double size; private boolean sdCard; private boolean replacableBattery; private int camera; public Phone(long id,String brand, String model, State state, double price, double size, boolean sdCard, boolean replacableBattery,int camera){ this.id = id; this.brand = brand; this.model = model; this.state = state; this.price = price; this.size = size; this.sdCard =sdCard; this.replacableBattery = replacableBattery; this.camera = camera; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getModel() { return model; } public void setModel(String model) { this.model = model; } public State getState() { return state; } public void setState(State state) { this.state = state; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public double getSize() { return size; } public void setSize(double size) { this.size = size; } public boolean isSdCard() { return sdCard; } public void setSdCard(boolean sdCard) { this.sdCard = sdCard; } public boolean isReplacableBattery() { return replacableBattery; } public void setReplacableBattery(boolean replacableBattery) { this.replacableBattery = replacableBattery; } public int getCamera() { return camera; } public void setCamera(int camera) { this.camera = camera; } @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; Phone other = (Phone) obj; if (id != other.id) return false; return true; } @Override public String toString() { return "Phone [id=" + id + ", brand=" + brand + ", model=" + model + ", state=" + state + ", price=" + price + ", size=" + size + ", sdCard=" + sdCard + ", replacableBattery=" + replacableBattery + ", camera=" + camera + "]"; } }
Printer.java
package com.websystique.springmvc.model; public class Printer{ private long id; private String brand; private String model; private State state; private double price; private String paperFormat; private String technologie; private int pagesPerMinute; public Printer(long id,String brand, String model, State state, double price, String paperFormat, String technologie, int pagesPerMinute){ this.id = id; this.brand = brand; this.model = model; this.state = state; this.price = price; this.paperFormat = paperFormat; this.technologie =technologie; this.pagesPerMinute = pagesPerMinute; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getModel() { return model; } public void setModel(String model) { this.model = model; } public State getState() { return state; } public void setState(State state) { this.state = state; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public String getPaperFormat() { return paperFormat; } public void setPaperFormat(String paperFormat) { this.paperFormat = paperFormat; } public String getTechnologie() { return technologie; } public void setTechnologie(String technologie) { this.technologie = technologie; } public int getPagesPerMinute() { return pagesPerMinute; } public void setPagesPerMinute(int pagesPerMinute) { this.pagesPerMinute = pagesPerMinute; } @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; Printer other = (Printer) obj; if (id != other.id) return false; return true; } @Override public String toString() { return "Printer [id=" + id + ", brand=" + brand + ", model=" + model + ", state=" + state + ", price=" + price + ", paperFormat=" + paperFormat + ", technologie=" + technologie + ", pagesPerMinute=" + pagesPerMinute + "]"; } }
State.java
package com.websystique.springmvc.model; public enum State { NEW,USED; }
CORS Filter: Creating Filter to handle Same Origin Policy related issues
package com.websystique.springmvc.configuration; 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 CORSFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; 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, res); } public void init(FilterConfig filterConfig) {} public void destroy() {} }
Look at how we are registering CORS filter with Spring configuration, which will help us to get away with Same Origin Policy issues.
package com.websystique.springmvc.configuration; import javax.servlet.Filter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class HelloWorldInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { HelloWorldConfiguration.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } @Override protected Filter[] getServletFilters() { Filter [] singleton = { new CORSFilter() }; return singleton; } }
Now build the war (either by eclipse as was mentioned in previous tutorials) or via maven command line( mvn clean install). Deploy the war to a Servlet 3.0 container . Since here i am using Tomcat, i will simply put this war file into tomcat webapps folder and click on startup.bat inside tomcat/bin directory.
Open browser and browse at localhost:8080/Spring4MVCAngularJSRoutingWithUIRouterExample/. it will be redirected to /category [look at $urlRouterProvider.otherwise(“/category”) part in $stateProvider setup]. Check the URL in browser.
In above interaction, A request was sent to server to fetch the list of categories & another request was send to server to fetch corresponding template. You can open the developer tools in your browser and check the request sent for each operation to server and response received. In this case we got the list of categories.
Now click on Computers link, a request is sent to server to get the list of items for category ‘computers’. Again, a seperate request is sent to fetch the respective template for that category. On response, you should see all computers list below main panel. You can see that URL ‘/category/Computers’ reflects current state. You can even manually change URL, hit enter, and see the respective response coming back.
Now click on specific computer. Again a request will be sent to fetch the details of an item based on itemId and category, and another request is sent to fetch corresponding item details template. URL ‘/category/Computers/2’ reflects it.
That’s it.
References
If you like tutorials on this site, why not take a step further and connect me on Facebook , Google Plus & Twitter as well? I would love to hear your thoughts on these articles, it will help improve further our learning process.
In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…
Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…
Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…
Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…
Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…
This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…
View Comments
Hello Sir can you help me Master + Details form insertion in database where more than 10 or 15 rows will insert into database. I cordially respect you. Please see the attachment form.
If someone help me I will pleased you forever .............. https://uploads.disquscdn.com/images/7cf019291390ad334515531b5dc96ea85d3214c89675e7e4d45087bfd9446c34.png
It's resolved just added the maven dependencies to web deployment assembly.
thank you
hello,
Thanks for the example,
But I have a problem running it with eclipse neon it shows nothing and the project is having a root error.
Is there any solution for this.
I use clean install to deploy the application with maven.
Hi Websystique
i have a little problem with this implementations.
when i access : /exemple#/list from the angularjs menu(nav-bar) i get the perfect response in the ui-view
.... but when i use the following url localhost:8080/myApp/exemple/list ; i obtain the list in a html page; not in the ui-view,
that's because the template controller return "exemple/list" ;
can you help me !
this is an example !
If anyone had problems with loading and running the sample application (downloaded at the end of this wonderful tutorial), I suggest you doing the following:
1) Import the downloaded project from the archive into your workspace.
2) Create a new Maven project with webapp archetype (as described earlier here: http://websystique.com/maven/create-a-maven-web-project-with-eclipse/)
3) Override the pom.xml of the new project with the one contained in the project you downloaded.
4) Rename the src/main/resources folder of the new project for src/main/java.
5) Copy the content of the src/main/java folder from the downloaded project into the new project.
6) Delete the content of the webapp folder in the new project (within the 'Deployed Resources' folder).
7) Copy the content of the webapp folder from the downloaded project into the new project (within the 'Deployed Resources' folder, of course the structure should be: 'Deployed Resources'/webapp/copied folders and files).
8) Update the new project due to the Maven properties. (Right click on the new project name in the Project / Package Explorer View of Eclipse -> Maven -> Update Project..)
9) Deploy the new project on the server.
10) Enjoy.
Note: I referred with the term 'new project' to the new Maven project (with webapp archetype) you created at the beginning; while with the term 'downloaded project' I refer to the one that is contained in the archive downloaded from the end of this tutorial.
Hi Ben,
Why would you preform all these steps. It works as it is, just tried it. It's a standard mvn web project.
Hi websystique,
That's because when I tried to deploy the downloaded project in my Eclipse EE Neon, deployed on the apache-tomcat-8.0.36 (through Eclipse) I got the same errors (did not see anything) as Kumar in his/her post below.
The solution above worked for the ui-router example, but for the ngRoute example it did not work at all. (Neither did the original project I downloaded from the website.)
Hi Ben, Did you try Setting up Tomcat with Eclipse? After importing the downloadable [from this post] into your Eclipse IDE, you can follow the link i shared. Tomcat setup could be the reason in your case.
Hi websystique , Great job with the tutorials . Although I am having hardest time using ur example application on tomcat8 . Except the project in which view is .jsp file (Spring 4 MVC+AngularJS CRUD Application using ngResource) no other project (i.e. UIRouter or ngRoute) is working . It gets successfully deployed but doesn't show anything on that URL . I have tried on both windows(tomcat8) and linux(tomcat7). Please refer to below screenshots .
Hi Kumar, Sorry i missed your post, found back (thanks to Ben). As i asked from Ben, Tomcat setup with Eclipse could be the problem you faced in your IDE.
Hi it was excellent learning through your site, I do have one small issue with above app, I am getting error in index.html file while referring for static files, the JS files and CSS are not loading.
Hi Veera,
Make sure you have configured the resource handler in Configuration class.
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
Additionally, do note that the resources folder is inside webapp.