Creating java web applications with AngularJS and Struts2

This post likes to show you how simple it is to create a new web application based on the client side JavaScript framework AngularJS and the server side Java framework Struts2.

Why this Struts2 - AngularJS framework combination

Struts2 is a MVC framework to build server side Java web applications. Struts2 is stable, easy to use and active developed and maintained since years. AngularJS is a modern client side MVC framework created and maintained by Google. Both together is a great way to develop stable web applications. You can mix single-page with multi-page web applications like multi-page for user sites and single-page for administration sites. Struts2 can be used for back-end actions as JSON data provider, for file-uploads, streaming actions, security, text provider and more. And of course with the flexible structure of Interceptor Stacks and namespaces you are able to develop extendable good scalable software. On the other side is AngularJS one of the most used front-end frameworks with good tutorials and documentation. The only disadvantage of this combination is you have to master both Java and JavaScript.

Create initial project structure

To create the initial project structure we are using the widely used build framework maven. For details about how to setup and install maven please read following blog post 4 Easy Steps to create a Java based Web Application with Struts2 and jQuery.

mvn archetype:generate -B -DgroupId=com.jgeppert.samples \
                            -DartifactId=struts2-angularjs\
                            -DarchetypeGroupId=org.apache.struts \
                            -DarchetypeArtifactId=struts2-archetype-angularjs \
                            -DarchetypeVersion=2.5.5 \
                            -DremoteRepositories=http://struts.apache.org

The Struts2 parts

Lets take a look at the generated Struts2 parts. First we have the struts.xml in which we can configure our Struts2 application like namespaces, interceptors and other defaults. In our created project structure this struts.xml is really small because we are using the Struts2 Convention Plugin to define our actions via conventions or annotations and most defaults are fine for the start.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
	"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
 
  <constant name="struts.enable.DynamicMethodInvocation" value="false"/>
  <constant name="struts.devMode" value="true"/>
 
  <constant name="struts.convention.default.parent.package" value="angularstruts"/>
  <package name="angularstruts" extends="json-default">
    <default-action-ref name="index" />
  </package>
  <package name="data" extends="angularstruts" namespace="/data"></package>
 
</struts>

This initial project structure includes two Struts2 actions. The first one is the Index action class which is the entry point for this application. According to the Struts2 Convention plugin defaults, the corresponding JSP page is located under WEB-INF/content/index.jsp.

<!DOCTYPE html>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html lang="en" ng-app="app">
<head>
    <meta charset="utf-8">
    <title>My AngularJS Struts2 App</title>
 
    <base href="<s:url forceAddSchemeHostAndPort="true" includeContext="true" value="/" namespace="/" />">
</head>
<body>
 
<h2><s:property value="message"/></h2>
 
<div>
    <a href="/home">Home</a> - <a href="/projects">Projects</a>
</div>
 
<div ng-controller="AppController as app">
    <div ng-view></div>
</div>
 
<s:if test="useMinifiedResources">
    <script src="<s:url value="js/external.js" />"></script>
    <script src="<s:url value="js/application.js" />"></script>
</s:if>
<s:else>
    <script src="<s:url value="js/lib/angular/angular.min.js" />"></script>
    <script src="<s:url value="js/lib/angular/angular-route.min.js" />"></script>
    <script src="<s:url value="js/app.js" />"></script>
    <script src="<s:url value="js/config.js" />"></script>
    <script src="<s:url value="js/services/DataService.js" />"></script>
    <script src="<s:url value="js/controllers/AppController.js" />"></script>
    <script src="<s:url value="js/controllers/HomeController.js" />"></script>
    <script src="<s:url value="js/controllers/ApacheProjectsController.js" />"></script>
</s:else>
</body>
</html>

This JSP is the entry point for the AngularJS part. In the head section we just define the base context of our application and at the end of the JSP we define all JavaScript resources we need.

The next Action, named ProjectsAction in the actions.data package, with the @Result annotation of type json marks this action as a JSON action. This action provides the frontend a small lists of sample Apache projects in the JSON format.

@Result(type = "json")
public class ProjectsAction extends ActionSupport {
 
    private static final long serialVersionUID = 9037336532369476225L;
    private static final Logger log = LogManager.getLogger(ProjectsAction.class);
 
    private List<String> projectNames;
 
    public String execute() throws Exception {
 
        projectNames = new ArrayList<String>();
        projectNames.add("Apache Struts");
        projectNames.add("Apache Log4j");
        projectNames.add("Apache Tomcat");
        projectNames.add("Apache Maven");
        projectNames.add("Apache Ant");
        projectNames.add("Apache Log4Net");
        projectNames.add("Apache Log4Cxx");
        projectNames.add("Apache Chainsaw");
        projectNames.add("Apache Incubator");
        projectNames.add("Apache Hadoop");
        projectNames.add("Apache OpenOffice");
        projectNames.add("Apache Mahout");
        projectNames.add("Apache Tapestry");
        projectNames.add("Apache Jena");
        projectNames.add("Apache Solr");
        projectNames.add("Apache Cayenne");
        projectNames.add("Apache OpenEJB");
        projectNames.add("Apache Deltaspike");
        projectNames.add("Apache Cordova");
 
        log.debug("Return {} Apache projects", projectNames.size());
 
        return SUCCESS;
    }
 
    public List<String> getProjectNames() {
        return projectNames;
    }
}

The AngularJS parts

To step into the frontend part, we can take a look at the generated javascript resources for the app app.js and config.js, DataService.js and the controllers.

app.js

The file app.js contains the initial configuration of our AngularJS application. First we define our module app with an dependency to ngRoute module, which manages the url mapping.

(function() {
    'use strict';
 
    angular
        .module('app', ['ngRoute']);
})();

config.js

With the $locationProvider we can setup our application to use the HTML5 mode URL's without the # sign symbol. And with the $routeProvider (provided by the ngRoute module) we define our mapping between URL, JavaScript controllers (defined in the controller.js) and the html partials which should be shown.

(function() {
    'use strict';
 
    angular
    .module('app')
        .config(['$routeProvider', '$locationProvider',
            function($routeProvider, $locationProvider) {
 
                $locationProvider.html5Mode(true).hashPrefix('!');
 
                $routeProvider.when('/projects', {
                    templateUrl: 'partials/projects.html',
                    controller: 'ApacheProjectsController as vm'
                }).when('/home', {
                    templateUrl: 'partials/home.html',
                    controller: 'HomeController as vm'
                }).otherwise({ redirectTo: '/home' });
            }
        ]);
})();

DataService.js

The folder js/services contains the services we need in our frontend. In the generated application it only contains one DataService.js which handles the request to the Project Action. This service contains a general asynchron request handling with promises.

(function() {
    'use strict';
 
    angular
        .module('app')
        .factory('DataService', DataService);
 
    function DataService($http, $log, $q) {
 
        /** Object to manage all backend URL's */
        var urls = {
            projects : "data/projects"
        };
 
        /** The DataService with all public methods */
        var service = {
            getProjects: getProjects
        };
 
        return service;
 
        /** Get all projects */
        function getProjects() {
            return _request(urls.projects);
        }
 
 
        /** A generic helper method to execute a HTTP request to the backend */
        function _request(url, method, model){
            var def = $q.defer(),
                req = {
                method: method,
                url: url
            };
 
            if(!method) {
                req.method = 'GET';
            }
 
            if(model) {
                req.data = model;
            }
            $http(req).success(function(data) {
                def.resolve(data);
            }).error(function(data, code) {
                def.reject(data);
                $log.error(data, code);
            });
            return def.promise;
        }
 
    }
 
})();

Controllers

In the folder controllers we can find all AngularJS controllers we currently use in the application.

AppController

In the AppController.js file the main entry point of the application is defined. In this example the AppController does not contain any logic.

(function() {
    'use strict';
 
    angular
        .module('app')
        .controller('AppController', AppController);
 
    function AppController() {
 
    }
})();

HomeController

In the file HomeController.js the view for the initial home page is defined and the scope variable vm.name is initialized with the value Sunshine.

(function() {
    'use strict';
 
    angular
        .module('app')
        .controller('HomeController', HomeController);
 
    function HomeController() {
        var vm = this;
        vm.name = "Sunshine";
    }
})();

ApacheProjectsController

The ApacheProjectsController.js is using the DataService which is defined in the services. This Data Service retrieves the JSON array of Apache projects from the Struts2 backend and assign this value to the vm.projects variable.

(function() {
    'use strict';
 
    angular
        .module('app')
        .controller('ApacheProjectsController', ApacheProjectsController);
 
    function ApacheProjectsController($log, DataService) {
        var vm = this;
 
        init();
 
        function init() {
            return DataService.getProjects().then(function(data) {
                vm.projects = data.projectNames;
                return vm.projects;
            }, function() {
                $log.error('Could not receive project names.');
            });
        }
    }
})();

The AngularJS view part

In the webapp/partials folder we find two partials one is the home.html and the other is finally the projects.html partials which generates the html by iterating over the vm.projects variable, we defined in the ApacheProjectsController, with the AngularJS ng-repeat directive.

<ul>
    <li ng-repeat="project in vm.projects">{{project}}</li>
</ul>

What's next?

With this basic setup we are able to simple create new Struts2 backend actions to provide the data we need in the frontend. Next steps could be:

  • Handle internationalization (i18n) in Struts2 and AngularJS
  • Validation and error handling
  • Resource managing (concatenation and minify of our javascript resources)
  • Use Struts2 REST plugin for backend actions

1 Comment

  1. Pingback: Struts2 AngularJS integration Tutorial | Struts AngularJs

Leave a Reply