Spring Framework Angularjs Integration Tutorial
Spring Application Framework enables us develop RESTful services using its Web MVC subsystem, and we can consume those services via any of the client side front end UI technologies, e.g Angular, Backbone or React JS. In this example, I will explain how a CRUD like scenario can be developed using Spring MVC at the back side, and Angular JS in front.
We will use latest versions of JDK 8 and Spring Tool Suite IDE and Angular JS in order to develop our example web application. You can download and install latest versions from here, here and here.
Table Of Contents
- 1. Create a new Maven WebApp project
- 2. Add necessary dependencies
- 3. Create domain model, DAO and service classes
- 4. Create REST Controller layer
- 5. Create Configuration and WebApplicationInitializer classes
- 6. Create db.sql
- 7. Deploy the example web application
- 8. Extract and install Angular JS
- 9. Create app and user folders within src/main/webapp folder
- 10. Create app.module.js file within app folder
- 11. Create user.module.js file within user folder
- 12. Create app.config.js file within app folder
- 13. Create user.service.js file within user folder
- 14. Create “user-list” and “user-detail” modules
- 15. Create user-list.template.html and user-detail.template.html files
- 16. Create Controller files
- 17. Modify index.jsp
- 18. Summary
- 19. Download the Source Code
Let’s start developing server side part of our example application as the first step.
1. Create a new Maven WebApp project
Create a new maven web app project called as “example” within the STS IDE.
2. Add necessary dependencies
Add necessary dependencies within the pom.xml file of the project.
pom.xml
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.3.5.RELEASE</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.7.1-1</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.192</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.7.Final</version> </dependency>
3. Create domain model, DAO and service classes
User.java
package com.example.model; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="t_user") public class User { @Id @GeneratedValue(strategy=GenerationType.SEQUENCE) private Long id; @Column(name="first_name") private String firstName; @Column(name="last_name") private String lastName; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((firstName == null) ? 0 : firstName.hashCode()); result = prime * result + ((lastName == null) ? 0 : lastName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; User other = (User) obj; if (firstName == null) { if (other.firstName != null) return false; } else if (!firstName.equals(other.firstName)) return false; if (lastName == null) { if (other.lastName != null) return false; } else if (!lastName.equals(other.lastName)) return false; return true; } }
Our domain class is super simple. It has only two attributes; “firstName” and “lastName”. We have also a simple UserDao interface as well.
UserDao.java
package com.example.dao; import java.util.List; import com.example.model.User; public interface UserDao { public List<User> findAll(); public User findById(Long id); public void save(User user); public void update(User user); public void delete(Long id); }
HibernateUserDao.java
package com.example.dao; import java.util.List; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import com.example.model.User; @Repository public class HibernateUserDao implements UserDao { @Autowired private SessionFactory sessionFactory; @SuppressWarnings("unchecked") public List<User> findAll() { return sessionFactory.getCurrentSession().createQuery("from User").getResultList(); } public User findById(Long id) { return sessionFactory.getCurrentSession().find(User.class, id); } public void save(User user) { sessionFactory.getCurrentSession().save(user); } public void update(User user) { sessionFactory.getCurrentSession().update(user); } public void delete(Long id) { sessionFactory.getCurrentSession().delete(sessionFactory.getCurrentSession().getReference(User.class, id)); } }
We will employ Hibernate in order to perform CRUD operations on our domain instances. Therefore, we created HibernateUserDao concrete class as above which implements UserDao interface we already defined. SessionFactory bean was injected within the dao bean so that persistence operations could be performed with Hibernate Session.
UserService.java
package com.example.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.dao.UserDao; import com.example.model.User; @Service @Transactional public class UserService { @Autowired private UserDao userDao; public List<User> findAll() { return userDao.findAll(); } public User findById(Long id) { return userDao.findById(id); } public void save(User user) { userDao.save(user); } public void update(User user) { userDao.update(user); } public void delete(Long id) { userDao.delete(id); } }
We also created a service class and inject UserDao bean into it. Our service class is only a thin layer which just delegates to its DAO counterpart in addition to making each public service method call transactional.
4. Create REST Controller layer
UserController.java
package com.example.web; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; 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.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.example.model.User; import com.example.service.UserService; @RestController @RequestMapping("/rest") public class UserController { @Autowired private UserService userService; @RequestMapping(value="/users",method=RequestMethod.GET) public List<User> findAll() { return userService.findAll(); } @RequestMapping(value="/users/{id}",method=RequestMethod.GET) public User findById(@PathVariable Long id) { return userService.findById(id); } @RequestMapping(value="/users",method=RequestMethod.POST) @ResponseStatus(code=HttpStatus.CREATED) public void save(@RequestBody User user) { userService.save(user); } @RequestMapping(value="/users",method=RequestMethod.PUT) @ResponseStatus(code=HttpStatus.OK) public void update(@RequestBody User user) { userService.update(user); } @RequestMapping(value="/users/{id}",method=RequestMethod.DELETE) @ResponseStatus(code=HttpStatus.OK) public void delete(@PathVariable Long id) { userService.delete(id); } }
It will be a simple REST controller class which is going to handle our GET, POST, PUT and DELETE HTTP method calls towards “/users” URI, and delegate the job to the service layer.
As there is no JAXB annotation within our domain model class User, and we have already included Jackson JSON library within our classpath, Spring Web MVC at runtime converts return values to JSON format and vice versa.
5. Create Configuration and WebApplicationInitializer classes
Create Configuration and WebApplicationInitializer classes in order to configure and bootstrap our Spring enabled web application on server side.
ExampleWebMvcConfig.java
package com.example.config; import javax.sql.DataSource; import org.hibernate.SessionFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.orm.hibernate5.HibernateTransactionManager; import org.springframework.orm.hibernate5.LocalSessionFactoryBuilder; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration @EnableWebMvc @EnableTransactionManagement @ComponentScan(basePackages = "com.example") public class ExampleWebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/angular-1.6.1/**").addResourceLocations("/angular-1.6.1/"); registry.addResourceHandler("/app/**").addResourceLocations("/app/"); registry.addResourceHandler("/user/**").addResourceLocations("/user/"); } @Bean public PlatformTransactionManager transactionManager(SessionFactory sessionFactory) { return new HibernateTransactionManager(sessionFactory); } @Bean public SessionFactory sessionFactory(DataSource dataSource) { return new LocalSessionFactoryBuilder(dataSource).scanPackages("com.example.model") .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect").buildSessionFactory(); } @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).addScript("classpath:db.sql").build(); } }
ExampleWebAppInitializer.java
package com.example.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class ExampleWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{ExampleWebMvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
6. Create db.sql
Create db.sql with the following content within src/main/resources file. This file is going to be processed by Spring’s EmbeddedDataBaseBuilder in order to create in memory H2 DataSource instance.
db.sql
CREATE USER IF NOT EXISTS SA SALT 'a9160799b9bc9c38' HASH '55cdff35bf0a2fe6bf8d88ab8eba4911cc8fac325176cd02f0498640181089cc' ADMIN; CREATE SEQUENCE PUBLIC.HIBERNATE_SEQUENCE START WITH 3; CREATE CACHED TABLE PUBLIC.T_USER( ID BIGINT NOT NULL, FIRST_NAME VARCHAR(255), LAST_NAME VARCHAR(255) ); ALTER TABLE PUBLIC.T_USER ADD CONSTRAINT PUBLIC.CONSTRAINT_9 PRIMARY KEY(ID); INSERT INTO PUBLIC.T_USER(ID, FIRST_NAME, LAST_NAME) VALUES (1, 'John', 'Doe'), (2, 'Joe', 'Doe');
7. Deploy the example web application
We can now deploy the example web application into the “tc Server”, already configured within the STS IDE. You can check that your application has been deployed and bootstrapped correctly by typing http://localhost:8080/example/rest/users in your browser’s URL toolbar. You should have seen JSON response produced by the REST service you invoked.
At this point our server side is ready to serve for the client side. Let’s create our front end now using Angular JS. Angular JS is a client side JS framework in order to develop single page web applications based on MVC architectural pattern. Simply saying, View is generated through Controller at runtime via consuming Model data. HTML pages are used as templates to generate view representations at runtime. Controller is in charge of interacting with backend services through REST API calls. It is a good practice to partition a web application developed using Angular JS into several modules/folders and sub modules each containing several related JS and HTML template files of their own.
8. Extract and install Angular JS
Extract Angular JS zipped file into the src/main/webapp folder. You should now see a folder with name angular-1.6.1 within that folder.
9. Create app and user folders within src/main/webapp folder
Those folders will be main module folders in our application.
10. Create app.module.js file within app folder
app.module.js
angular.module('app',['ngRoute','user']);
This is the main “app” module which imports other dependent application or Angular specific modules in the application. “ngRoute” is an Angular module which is used to navigate through URIs, and “user” is application specific module within which our CRUD scenarios reside.
11. Create user.module.js file within user folder
user.module.js
angular.module('user',['ngResource','userList','userDetail']);
This is the “user” module file which combines two other sub modules namely “userList” and “userDetail” and Angular specific “ngResource” module which is used for REST API calls.
12. Create app.config.js file within app folder
app.config.js
angular.module('app').config(['$locationProvider','$routeProvider', function config($locationProvider,$routeProvider) { $routeProvider.when('/list', { template:'<user-list></user-list>' }).when('/edit/:id', { template:'<user-detail></user-detail>' }).when('/create',{ template:'<user-detail></user-detail>' }).otherwise('/list'); }]);
This JS file contains routing information. When URI fragment contains “#!/list”, <user-list> module will be displayed on the screen, or when URI fragment contains either “#!/edit/:id” or “#!/create” <user-detail> module will be displayed.
13. Create user.service.js file within user folder
user.service.js
angular.module('user').factory('UserService',['$resource', function($resource) { return $resource( 'rest/users/:id', { }, { update:{method:'PUT'} }, { stripTrailingSlashes:true }); }]);
This JS makes use of Angular’s “$resource” service to define a REST service in order to perform REST API calls on the server side.
14. Create “user-list” and “user-detail” modules
Create “user-list” and “user-detail” modules sub folders within user folder, and place their corresponding module files under user-list and user-detail folders respectively.
user-list.module.js
angular.module('userList',[]);
user-detail.module.js
angular.module('userDetail',['ngRoute']);
15. Create user-list.template.html and user-detail.template.html files
We will bring controller and template files together within those module folders. Let’s create user-list.template.html and user-detail.template.html files within their respective folders with the following contents.
user-list.template.html
<button ng-click="$ctrl.create()">Create User</button> <br/> <br/> <table border="0"> <tr bgcolor="lightgray"> <td>ID</td> <td>First Name</td> <td>Last Name</td> <td>Action</td> </tr> <tr ng-repeat="user in $ctrl.users"> <td>{{user.id}}</td> <td>{{user.firstName}}</td> <td>{{user.lastName}}</td> <td> <button ng-click="$ctrl.edit(user.id)">Edit</button> </td> </tr> </table>
We will list all users returned by our REST API call “/users” and display them in tabular format. Each row will have an Edit action button. We will be able to navigate to “user-detail” view by clicking it. There is also a Create User button above the table in order to create a new User. This will also take us to “user-detail” view as well, however with a slightly different UI state.
user-detail.template.html
<table border="0"> <tr> <td bgcolor="lightgray">First Name:</td> <td><input ng-model="$ctrl.user.firstName"/></td> </tr> <tr> <td bgcolor="lightgray">Last Name:</td> <td><input ng-model="$ctrl.user.lastName"/></td> </tr> </table> <button ng-show="!$ctrl.user.id" ng-click="$ctrl.save()">Save</button> <button ng-show="$ctrl.user.id" ng-click="$ctrl.update()">Update</button> <button ng-show="$ctrl.user.id" ng-click="$ctrl.delete()">Delete</button>
When Edit button is clicked a REST API call will be made to “/users/{id}” URI with HTTP GET method, and User details will be shown in the page. You can either decide to change user’s properties or delete the user. When Update button is clicked, a REST API call will be made to “/users/{id}” URI with HTTP PUT method in order to update contents of the user record. When Delete button is clicked, a REST API call will be made to “/users/{id}” with HTTP DELETE method in order to delete the user record from DB.
If the detail view is navigated through “#!/create”, then Create button will be visible instead, and whenever you click Create button after filling out input fields, a new User will be created by invoking REST API call towards “/users” URI with HTTP POST method.
16. Create Controller files
All the above work is handled within controllers in Angular, and let’s create our controllers as follows.
user-list.component.js
angular.module('userList').component('userList', { templateUrl:'user/user-list/user-list.template.html', controller: ['$location','UserService',UserListController] }); function UserListController($location,UserService) { var self = this; self.users = UserService.query(); self.create = function() { $location.path('/create'); }; self.edit = function(id) { $location.path('/edit/' + id); }; }
user-detail.component.js
angular.module('userDetail').component('userDetail',{ templateUrl:'user/user-detail/user-detail.template.html', controller:['$routeParams','$location','UserService',UserDetailController] }); function UserDetailController($routeParams,$location,UserService) { var self = this; self.user = $routeParams.id?UserService.get({id:$routeParams.id}):null; self.save = function() { UserService.save(self.user,function() { $location.path('/list'); }); }; self.update = function() { UserService.update(self.user, function() { $location.path('/list'); }); }; self.delete = function() { UserService.delete({id:self.user.id}, function() { $location.path('/list'); }); }; }
We simply bound controllers with their templates together in those files, define functions to handle view navigation requests, and perform REST API service calls.
17. Modify index.jsp
index.jsp
<html ng-app="app"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Example Web Application</title> <script type="text/javascript" src="angular-1.6.1/angular.js"></script> <script type="text/javascript" src="angular-1.6.1/angular-route.js"></script> <script type="text/javascript" src="angular-1.6.1/angular-resource.js"></script> <script type="text/javascript" src="app/app.module.js"></script> <script type="text/javascript" src="app/app.config.js"></script> <script type="text/javascript" src="user/user.module.js"></script> <script type="text/javascript" src="user/user-list/user-list.module.js"></script> <script type="text/javascript" src="user/user-detail/user-detail.module.js"></script> <script type="text/javascript" src="user/user.service.js"></script> <script type="text/javascript" src="user/user-list/user-list.component.js"></script> <script type="text/javascript" src="user/user-detail/user-detail.component.js"></script> </head> <body> <div ng-view></div> </body> </html>
It lists JS files we already created and define the place which will be controlled by our Angular module “app”.
That’s all we need to do integrate Angular JS with Spring Web MVC. Let’s give it a try by typing http://localhost:8080/example. You should have seen Users listed. You can create a new User, edit or delete an existing one.
18. Summary
In this installment, we created a simple REST API to perform CRUD operation via Spring Web MVC, and developed a client side using Angular JS to consume those REST services we created.
19. Download the Source Code
You can download the full source code of this example here: Spring with Angular JS Example