AngularJS $http service is used for asynchronous server communication. This post explains $http
service in great details, specially $q
& promise
, with a complete CRUD application example, using AngularJS front-end client, communicating asynchronously with Spring REST API based back-end. You can plug-in your own server with this client to play with.
$resource
service instead. If you want to learn $resource, a complete post with CRUD example is available at Angularjs Server Communcation using ngResource-CRUD application example.
To learn more about Spring REST API in general, don’t forget to visit Spring REST API Tutorial
What is $http
AngularJS $http service allows us to communicate asynchronously with server endpoints using XHR [browser’s XMLHttpRequest Object] API. $http is designed for general purpose AJAX calls that can deal with both RESTful & Non-RESTful API on your server. For anyone coming from jQuery background, $http can be treated somewhat equivalent to jQuery.ajax. The $http API is based on the deferred/promise APIs exposed by the $q service which is an implementation of Promise interface
, based on Kris Kowal’s Q approach, which is a standardized way of dealing with asynchronous calls in JavaScript.
The Promise API comes with following flow:
- 1. Each asynchronous task will return a promise object.
- 2. Each promise object will have a then function that can take two arguments, a success handler and an error handler.
- 3. The success or the error handler in the then function will be called only once, after the asynchronous task finishes.
- 4. The then function will also return a promise, to allow chaining multiple calls.
- 5. Each handler (success or error) can return a value, which will be passed to the next function in the chain of promises.
- 6. If a handler returns a promise (makes another asynchronous request), then the next handler (success or error) will be called only after that request is finished.
Let’s take a simple example to understand $http.
// Simple GET request example : $http.get('/someUrl'). then(function(response) { // this callback will be called asynchronously when the response is available }, function(response) { // this callback will be called asynchronously if an error occurs or server returns response with an error status. });
In above example, then function takes two callbacks as parameter. First parameter gets called if the response is success. In case of failure, second parameter gets called. Both of callback parameters takes response as input.
The response object has these properties:
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
What about Server
Our Server is Spring Based REST API, taken from this post. Basically, This is what our REST API does:
GET request to /api/user/ returns a list of users
GET request to /api/user/1 returns the user with ID 1
POST request to /api/user/ with a user object as JSON creates a new user
PUT request to /api/user/3 with a user object as JSON updates the user with ID 3
DELETE request to /api/user/4 deletes the user with ID 4
DELETE request to /api/user/ deletes all the users
For this post we will reuse the Server from mentioned post. We will just tweak the properties in User model object to meet the client from this example.
1. Client Part
Declare Module [app.js
]
'use strict'; var App = angular.module('myApp',[]);
Create Service [user_service.js
]
Let’s create the service (using module.factory as discussed in AngularJs Service Post). This service will then be injected in controller.
'use strict'; App.factory('UserService', ['$http', '$q', function($http, $q){ return { fetchAllUsers: function() { return $http.get('http://localhost:8080/SpringMVC4RestAPI/user/') .then( function(response){ return response.data; }, function(errResponse){ console.error('Error while fetching users'); return $q.reject(errResponse); } ); }, createUser: function(user){ return $http.post('http://localhost:8080/SpringMVC4RestAPI/user/', user) .then( function(response){ return response.data; }, function(errResponse){ console.error('Error while creating user'); return $q.reject(errResponse); } ); }, updateUser: function(user, id){ return $http.put('http://localhost:8080/SpringMVC4RestAPI/user/'+id, user) .then( function(response){ return response.data; }, function(errResponse){ console.error('Error while updating user'); return $q.reject(errResponse); } ); }, deleteUser: function(id){ return $http.delete('http://localhost:8080/SpringMVC4RestAPI/user/'+id) .then( function(response){ return response.data; }, function(errResponse){ console.error('Error while deleting user'); return $q.reject(errResponse); } ); } }; }]);
Create Controller [app_controller.js
]
Controllwe will have our service injected, which it will then use from inside it’s functions exposed to User interface.
'use strict'; App.controller('AppController', ['$scope', 'UserService', function($scope, UserService) { var self = this; self.user={id:null,username:'',address:'',email:''}; self.users=[]; self.fetchAllUsers = function(){ UserService.fetchAllUsers() .then( function(d) { self.users = d; }, function(errResponse){ console.error('Error while fetching Currencies'); } ); }; self.createUser = function(user){ UserService.createUser(user) .then( self.fetchAllUsers, function(errResponse){ console.error('Error while creating User.'); } ); }; self.updateUser = function(user, id){ UserService.updateUser(user, id) .then( self.fetchAllUsers, function(errResponse){ console.error('Error while updating User.'); } ); }; self.deleteUser = function(id){ UserService.deleteUser(id) .then( self.fetchAllUsers, function(errResponse){ console.error('Error while deleting User.'); } ); }; self.fetchAllUsers(); self.submit = function() { if(self.user.id===null){ console.log('Saving New User', self.user); self.createUser(self.user); }else{ self.updateUser(self.user, self.user.id); console.log('User updated with id ', self.user.id); } self.reset(); }; self.edit = function(id){ console.log('id to be edited', id); for(var i = 0; i < self.users.length; i++){ if(self.users[i].id === id) { self.user = angular.copy(self.users[i]); break; } } } self.remove = function(id){ console.log('id to be deleted', id); if(self.user.id === id) {//clean the form if the user to be deleted is shown there. self.reset(); } self.deleteUser(id); } self.reset = function(){ self.user={id:null,username:'',address:'',email:''}; $scope.myForm.$setPristine(); //reset Form } }]);
Create FE Application [ServiceExample.html]
It’s a trivial user management CRUD application with AngulasrJS, using bootstrap for styling. We have modularized it a bit, by keeping module,controller & service in different files.
<html> <head> <title>AngularJS $http Example</title> <style> .username.ng-valid { background-color: lightgreen; } .username.ng-dirty.ng-invalid-required { background-color: red; } .username.ng-dirty.ng-invalid-minlength { background-color: yellow; } .email.ng-valid { background-color: lightgreen; } .email.ng-dirty.ng-invalid-required { background-color: red; } .email.ng-dirty.ng-invalid-email { background-color: yellow; } </style> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="app.css"> </head> <body ng-app="myApp" class="ng-cloak"> <div class="generic-container" ng-controller="AppController as ctrl"> <div class="panel panel-default"> <div class="panel-heading"><span class="lead">User Registration Form </span></div> <div class="formcontainer"> <form ng-submit="ctrl.submit()" name="myForm" class="form-horizontal"> <input type="hidden" ng-model="ctrl.user.id" /> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-2 control-lable" for="uname">Name</label> <div class="col-md-7"> <input type="text" ng-model="ctrl.user.username" id="uname" class="username form-control input-sm" placeholder="Enter your name" required ng-minlength="3"/> <div class="has-error" ng-show="myForm.$dirty"> <span ng-show="myForm.uname.$error.required">This is a required field</span> <span ng-show="myForm.uname.$error.minlength">Minimum length required is 3</span> <span ng-show="myForm.uname.$invalid">This field is invalid </span> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-2 control-lable" for="address">Address</label> <div class="col-md-7"> <input type="text" ng-model="ctrl.user.address" id="address" class="form-control input-sm" placeholder="Enter your Address. [This field is validation free]"/> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-2 control-lable" for="email">Email</label> <div class="col-md-7"> <input type="email" ng-model="ctrl.user.email" id="email" class="email form-control input-sm" placeholder="Enter your Email" required/> <div class="has-error" ng-show="myForm.$dirty"> <span ng-show="myForm.email.$error.required">This is a required field</span> <span ng-show="myForm.email.$invalid">This field is invalid </span> </div> </div> </div> </div> <div class="row"> <div class="form-actions floatRight"> <input type="submit" value="{{!ctrl.user.id ? 'Add' : 'Update'}}" class="btn btn-primary btn-sm" ng-disabled="myForm.$invalid"> <button type="button" ng-click="ctrl.reset()" class="btn btn-warning btn-sm" ng-disabled="myForm.$pristine">Reset Form</button> </div> </div> </form> </div> </div> <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"><span class="lead">List of Users </span></div> <div class="tablecontainer"> <table class="table table-hover"> <thead> <tr> <th>ID.</th> <th>Name</th> <th>Address</th> <th>Email</th> <th width="100"></th> </tr> </thead> <tbody> <tr ng-repeat="u in ctrl.users"> <td><span ng-bind="u.id"></span></td> <td><span ng-bind="u.username"></span></td> <td><span ng-bind="u.address"></span></td> <td><span ng-bind="u.email"></span></td> <td> <button type="button" ng-click="ctrl.edit(u.id)" class="btn btn-success custom-width">Edit</button> <button type="button" ng-click="ctrl.remove(u.id)" class="btn btn-danger custom-width">Remove</button> </td> </tr> </tbody> </table> </div> </div> </div> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js"> </script> <script src="app.js"></script> <script src="user_service.js"></script> <script src="app_controller.js"></script> </body> </html>
2. Server Part
As mentioned before, we can create a Spring REST API using this post. We need to adapt User properties based on User Interface in this example.
Complete Server is shown below.
Spring REST API controller: This Controller returns JSON response [thanks to Jackson library in classpath].
package com.websystique.springmvc.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder; import com.websystique.springmvc.model.User; import com.websystique.springmvc.service.UserService; @RestController public class HelloWorldRestController { @Autowired UserService userService; //Service which will do all data retrieval/manipulation work //-------------------Retrieve All Users-------------------------------------------------------- @RequestMapping(value = "/user/", method = RequestMethod.GET) public ResponseEntity<List<User>> listAllUsers() { List<User> users = userService.findAllUsers(); if(users.isEmpty()){ return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND } return new ResponseEntity<List<User>>(users, HttpStatus.OK); } //-------------------Retrieve Single User-------------------------------------------------------- @RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<User> getUser(@PathVariable("id") long id) { System.out.println("Fetching User with id " + id); User user = userService.findById(id); if (user == null) { System.out.println("User with id " + id + " not found"); return new ResponseEntity<User>(HttpStatus.NOT_FOUND); } return new ResponseEntity<User>(user, HttpStatus.OK); } //-------------------Create a User-------------------------------------------------------- @RequestMapping(value = "/user/", method = RequestMethod.POST) public ResponseEntity<Void> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) { System.out.println("Creating User " + user.getUsername()); if (userService.isUserExist(user)) { System.out.println("A User with name " + user.getUsername() + " already exist"); return new ResponseEntity<Void>(HttpStatus.CONFLICT); } userService.saveUser(user); HttpHeaders headers = new HttpHeaders(); headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri()); return new ResponseEntity<Void>(headers, HttpStatus.CREATED); } //------------------- Update a User -------------------------------------------------------- @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT) public ResponseEntity<User> updateUser(@PathVariable("id") long id, @RequestBody User user) { System.out.println("Updating User " + id); User currentUser = userService.findById(id); if (currentUser==null) { System.out.println("User with id " + id + " not found"); return new ResponseEntity<User>(HttpStatus.NOT_FOUND); } currentUser.setUsername(user.getUsername()); currentUser.setAddress(user.getAddress()); currentUser.setEmail(user.getEmail()); userService.updateUser(currentUser); return new ResponseEntity<User>(currentUser, HttpStatus.OK); } //------------------- Delete a User -------------------------------------------------------- @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE) public ResponseEntity<User> deleteUser(@PathVariable("id") long id) { System.out.println("Fetching & Deleting User with id " + id); User user = userService.findById(id); if (user == null) { System.out.println("Unable to delete. User with id " + id + " not found"); return new ResponseEntity<User>(HttpStatus.NOT_FOUND); } userService.deleteUserById(id); return new ResponseEntity<User>(HttpStatus.NO_CONTENT); } //------------------- Delete All Users -------------------------------------------------------- @RequestMapping(value = "/user/", method = RequestMethod.DELETE) public ResponseEntity<User> deleteAllUsers() { System.out.println("Deleting All Users"); userService.deleteAllUsers(); return new ResponseEntity<User>(HttpStatus.NO_CONTENT); } }
Service Interface
package com.websystique.springmvc.service; import java.util.List; import com.websystique.springmvc.model.User; public interface UserService { User findById(long id); User findByName(String name); void saveUser(User user); void updateUser(User user); void deleteUserById(long id); List<User> findAllUsers(); void deleteAllUsers(); public boolean isUserExist(User user); }
Service Implementation.
package com.websystique.springmvc.service; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.websystique.springmvc.model.User; @Service("userService") @Transactional public class UserServiceImpl implements UserService{ private static final AtomicLong counter = new AtomicLong(); private static List<User> users; static{ users= populateDummyUsers(); } public List<User> findAllUsers() { return users; } public User findById(long id) { for(User user : users){ if(user.getId() == id){ return user; } } return null; } public User findByName(String name) { for(User user : users){ if(user.getUsername().equalsIgnoreCase(name)){ return user; } } return null; } public void saveUser(User user) { user.setId(counter.incrementAndGet()); users.add(user); } public void updateUser(User user) { int index = users.indexOf(user); users.set(index, user); } public void deleteUserById(long id) { for (Iterator<User> iterator = users.iterator(); iterator.hasNext(); ) { User user = iterator.next(); if (user.getId() == id) { iterator.remove(); } } } public boolean isUserExist(User user) { return findByName(user.getUsername())!=null; } public void deleteAllUsers(){ users.clear(); } private static List<User> populateDummyUsers(){ List<User> users = new ArrayList<User>(); users.add(new User(counter.incrementAndGet(),"Sam", "NY", "sam@abc.com")); users.add(new User(counter.incrementAndGet(),"Tomy", "ALBAMA", "tomy@abc.com")); users.add(new User(counter.incrementAndGet(),"Kelly", "NEBRASKA", "kelly@abc.com")); return users; } }
User Model class.
package com.websystique.springmvc.model; public class User { private long id; private String username; private String address; private String email; public User(){ id=0; } public User(long id, String username, String address, String email){ this.id = id; this.username = username; this.address = address; this.email = email; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @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 (!(obj instanceof User)) return false; User other = (User) obj; if (id != other.id) return false; return true; } @Override public String toString() { return "User [id=" + id + ", username=" + username + ", address=" + address + ", email=" + email + "]"; } }
Spring Configuration Class.
package com.websystique.springmvc.configuration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @EnableWebMvc @ComponentScan(basePackages = "com.websystique.springmvc") public class HelloWorldConfiguration { }
Spring Initializer class.
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; } }
CORS filter class : 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 { System.out.println("Filtering on..........................................................."); 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() {} }
Declare 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>SpringMVC4RestAPI</artifactId> <packaging>war</packaging> <version>1.0.0</version> <name>SpringMVC4RestAPI 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-tx</artifactId> <version>${springframework.version}</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> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warSourceDirectory>src/main/webapp</warSourceDirectory> <warName>SpringMVC4RestAPI</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </pluginManagement> <finalName>SpringMVC4RestAPI</finalName> </build> </project>
Project Structure
Deploy & Run.
Build and Deploy Server to any Servlet 3 environment. I am using Tomcat 8 here. Then run the server using bin/startup.bat in Tomcat.
Open the FE (ServerExample.html) created above. You will see list of users.
Play with create/edit/delete users to see them in action. Open Developer Tools in your browser to see how client and server is communicating using JSON.
Live Demo
Please note that this live demo is showing only UI of your application, backed with static user data.There is no live JVM attached with this live demo. Refresh the page to repopulate with initial data.
That’s it. Complete source code for this post can be downloaded from Spring 4 MVC+AngularJS Example.
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.