Spring 4 MVC+AngularJS Routing Example using UI-Router

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:

Spring4MVCAngularJSRoutingUiRouterExample_img1

Spring4MVCAngularJSRoutingUiRouterExample_img2

Spring4MVCAngularJSRoutingUiRouterExample_img3

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:

  • Spring 4.2.0.RELEASE
  • AngularJS 1.4.4
  • Maven 3
  • JDK 1.7
  • Eclipse Mars.1 Release (4.5.1)

Project Structure

Spring4MVCAngularJSRoutingUiRouterExample_img00
Spring4MVCAngularJSRoutingUiRouterExample_img01

Provide 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>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>

1. Client Side

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.

Route configuration:

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:

  • The $urlRouterProvider provides us the way to configure a default URL.
  • The $stateProvider provides us state(name,object) functions in order to define all states in one place. First argument is the name of state and second argument is an object which eventually may contain url/template/templateUrl/Controller/resolve and other functions.
  • States category.list & category.list.details are using regular expressions to match specific URL which will eventually include paramters [catagoryId , itemId]. We can get those parameters using $stateParams and then eventually pass them to server to get the specific content.
  • Look specially at how the resolve is used to trigger service for communicating servers. And eventually the promise(named ‘async’ above) is passed into controllers to fetch the category or specific item details.

For more in depth details, please refer to Angularjs Routing Tutorial using ui-router.

Service Creation:

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);
								}
						);
			}
	};

}]);

Controller Creation:

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.

Configure Welcome Page:

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:

  • Notice that hyperlink for the Categories uses 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.
  • A state with a dot in it’s name is considered as Nested State. In this case we say the category.list is a nested state of “category” state.
  • When a state is activated [by clicking on ui-sref hyperlink or directly typing the URL (mentioned in state configuration) in browser], its templates are automatically inserted into the ui-view of its parent state’s template. If it’s a top-level state—which ‘category’ is because it has no parent state–then its parent template is index.html. So in this case, this template will be inserted into ui-view defined in index.html.
  • We can also pass parameters to state using {parameterName:value} form. In this example, we pass categoryId as parameter using {categoryId:category} which refers to specific category. This parameter can then be accessed using $stateParams, and used to perform further logic.

Create Templates for Nested states:

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:

  • This templates is similar to the category template [all_categories.html] & it corresponds to nested state [category.list], & gets activated when clicking on ui-sref links in all_categories.html. Since category.list is a nested state, it’s template [this one] will be injected in ui-view of parent [category state : inside all_categories.html]
  • This template contains ui-sref which refers to it’s child state ‘category.list.detail’
  • We are also passing two parameters to the child state :categoryId we got from parent state category & item Id for a specific item
  • This template itself contains ui-view directive, which means further nested template [child of this state] can be injected here.

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>

2. Server Side

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() {}

}

Create Spring Initializer class

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;
	}
 
}

Deploy & Run

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.

Spring4MVCAngularJSRoutingUiRouterExample_img1

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.

Spring4MVCAngularJSRoutingUiRouterExample_img2

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.

Spring4MVCAngularJSRoutingUiRouterExample_img3

That’s it.

Download Source Code


References