Spring + REST + Angular: Consuming the API — Part 2 of 2

This series of posts builds a RESTful service that is consumed by an Angular front end. In this post we will create an Angular application that consumes the RESTful web service we built in Part 1 of the series. The source code for this sample application is on GitHub: https://github.com/thoward333/addrbook

Angular Overview

Angular is a JavaScript MV* framework. It is a very expressive framework and offers robust features such as dependency injection (DI), data binding, and templating. Angular can be used as a Single Page Architecture (SPA) application or used as a component in a traditional web application. For our Address Book application we will be using it to create a SPA application, which means the whole web site will be a single HTML page that uses AJAX to fetch data and dynamically update the page contents.

Project Layout

The Angular code for Address Book was based on the seed tutorial phonecat application.

All the Angular files are located in the src/main/webapp directory. By convention, Angular files name the main file app.js, which is in the js sub-directory. This file declares the Angular module and its dependencies. It also defines the routes, which bind templates to controllers. Be careful not to confuse Angular’s controllers (eg, HomeCtrl) with Spring’s controllers (eg, PersonController).

While all the rest of the Angular code could be added to app.js, the file can quickly become cluttered and so common convention is to create a separate file: controllers.js, filters.js, directives.js, and so on. This greatly improves the maintainability of the code. In our example we only have controllers, so we’ll omit the other files. We use Angular’s module dependency system to separate the files. Here app.js defines the main module with dependencies ngRoute and addressbookControllers:

var addressbookApp = angular.module('addressbookApp', [
	'ngRoute',
	'addressbookControllers'
]);

And the addressbookControllers module is declared in controllers.js:

var addressbookControllers = angular.module('addressbookControllers', []);

Now we need to add our addressbookApp module in index.html:

...

The ng-app directive on the <html> tag initializes the addressbookApp module at the root of the page, thus making this an SPA application. Alternatively, the addressbookApp module can be used as a component on the page by placing ng-app on any element in the <body> to use it. Lastly, the ng-view directive declares where to place the Angular template output.

Route

Routes are the lifeblood of Angular. This is how we bind URLs to our controllers and templates. In app.js we have declared a single route:

addressbookApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/home', {
        templateUrl: 'partials/home.html',
        controller: 'HomeCtrl'
      }).
      otherwise({
        redirectTo: '/home'
      });
  }
]);

Let’s break down this snippet. The $routeProvider variable is an Angular service, available here using dependency injection (DI). By convention, all of Angular’s services start with ‘$’ to help distinguish them from your own variables. It is recommended to never start your variables with ‘$’ to preserve this distinction.

Note that $routeProvider is declared twice. Angular’s DI system relies on variable names and so we could omit the ‘$routeProvider’, on the first line. However, if the code were minified–per JavaScript best practices–then the variable would be renamed and DI would be broken. Angular allows the functions variables to be provided as strings in the way to preserve the DI functionality. This is better demonstrated with the controller, which we’ll look at in the next section.

Lastly, The when/otherwise bind the URL ‘/home’ to the ‘partials/home.html’ template and ‘HomeCtrl’ controller. The otherwise declares the default route. If we were to more routes we would simply add more when clauses. Next, let’s look at the controller.

Controller

The controllers.js file defines the HomeCtrl that is bound in the route. Let’s look at its declaration:

addressbookControllers.controller('HomeCtrl', ['$scope', '$http',
  function($scope, $http) {
    ...
  }
]);

This defines ‘HomeCtrl’ as this given function. Note the use of DI for $scope and $http. As mentioned earlier, we declare the function’s parameters as strings to make them safe for minification. All these are Angular services and serve an important purpose:

  • $scope: This is used in conjunction with data binding in templates. We’ll discuss this more when we look at the template in the next section.
  • $http: Utility for invoking HTTP requests.

The first thing our HomeCtrl function does is declare a createPerson function for use in the template:

    $scope.createPerson = function() {
      $http.post('api/person', {
          "userName": $scope.userName
          ,"firstName": $scope.firstName
          ,"lastName": $scope.lastName
      })
      .success(function(data, status, headers, config) {
        $scope.userName = '';
        $scope.firstName = '';
        $scope.lastName = '';
        $scope.newUserId = data;
      })
      .error(function(data, status, headers, config) {
        console.log('error: data = ' , data);
      });
    };

The createPerson function makes an AJAX call to POST to ‘api/person’ a JSON object with properties userName, firstName, and lastName. The $http.post returns a Promise, which is an idiom for elegantly handling asynchronous responses. When the AJAX call completes, the ‘success’ or ‘error’ function is invoked as appropriate. The success function assigns new values to several $scope variables. We’ll discuss this further when we look at the template in the next section.

Template

The template is defined in home.html:

<form ng-submit="createPerson()">
  <p>Create a new user:</p>
  <p>
    Username: <input ng-model="userName" />
  </p>
  <p>
    First Name: <input ng-model="firstName" />
  </p>
  <p>
    Last Name: <input ng-model="lastName" />
  </p>
  <input type="submit" />
 
  <p ng-show="newUserId">
    User created with id: {{newUserId}}
  </p>
</form>

The ng-* attributes on some of the DOM elements are Angular directives. Directives are an extremely powerful feature of Angular, allowing very expressive dynamic HTML. They use data binding to detect when variables have been changed and automatically react to modify the HTML to reflect the changes. Remember in HomeCtrl we had $scope.userName, $scope.firstName, and $scope.lastName? Those variables correspond to the ng-model attributes on the <input> tags. This uses a two-way binding, which means that if the UI is updated then HomeCtrl’s variables are changed and vice versa. The ng-submit on the <form> tag invokes HomeCtrl’s $scope.createPerson function when the form is submitted. Lastly, the ng-show on the <p> tag allows us to access HomeCtrl’s $scope.newUserId as a template variable as {{newUserId}}.

Data Binding in Action

Combining what we looked at in HomeCtrl controller and home.html template, here’s what happens. Provide some data to each field (eg: Username=test1, First Name=Test, Last Name=One) and click the Submit button. This triggers the ng-submit directive, which invokes the createPerson defined in HomeCtrl ($scope.createPerson). This function performs does a POST to /api/person with this request body:

{userName:"test1",firstName:"Test",lastName:"One"}

Upon successful completion of the AJAX call, HomeCtrl assigns the $scope variables to the empty string, which clears the text on the page. Lastly, HomeCtrl assigns $scope.newUserId to be the return value from the AJAX response, which displays it on the page.