Posted on Feb 13th 2017
Connecting your Angular 2 App to your Java EE Backend
You’ve been developing in Java EE all this time, but you’re tired of using JSF, or perhaps even Struts, and want to move to something more modern for your front end? Angular 2 is a perfect fit, and with the recent release of Angular 2 GA, now is the ideal time to make the move! Besides being easier to develop and maintain, an Angular 2 application allows you to take advantage of the huge improvements in browser technology over the last few years, and all the goodness that the modern web brings.
This guide walks you through a few of the modifications you’ll need to make to your back-end Java EE application, and how we can access this back end from your Angular 2 application.
Broad Strokes
We will be working with two separate applications, a Java EE back-end app, and an Angular 2 front-end app, avoiding a mixing of concerns in a single project. Source for both projects is provided, so in this article we display and highlight source only for key concepts – you can download the projects to look at more detail, or run them locally.
Key technologies:
- JPA: Our sample Java EE application uses JPA to access data from a database.
- REST: We expose the table data via REST web services using Jersey – the reference implementation for the JAX-RS spec.
- RxJS: Our Angular 2 app uses RxJS to communicate with the web services.
On a remote dev team? Try CodeTogether—it’s free!
- Live share IDEs & coding sessions
- See changes in real time
- Cross-IDE support for VS Code, IntelliJ & Eclipse
- Guests join from Browser or IDE
- End-to-end source encryption
- www.codetogether.com
JPA
Our sample back-end application, SWBackend, uses JPA with EclipseLink as a provider, and uses an embedded Derby database. We’re initializing this database with some character information from Star Wars, so you don’t need to bother with setting up sample data for testing.
We won’t delve into the JPA aspects here, but you could also follow along with your own Java EE back-end application.
RESTifying Your Java EE Application
Dependencies
We need to add Jersey and supporting libraries to your project – these will enable the serialization of your data and the ability to expose it using REST web services.
If you are following along in MyEclipse 2017, simply bring up the context menu on your project and select Project >Configure Facets… > Install JAX-RS Facet and proceed to select version 2.0 of JAX-RS. On the next page of the wizard, ensure you select Moxy and the Server libraries too.
If you are using Maven, this doc covers the dependencies you will need to add for different use cases. Primarily though, we will need:
org.glassfish.jersey.core/jersey-client
org.glassfish.jersey.core/jersey-server
org.glassfish.jersey.containers/jersey-container-servlet
org.glassfish.jersey.media/jersey-media-moxy
If you are not using Maven, you can download the Jersey JAX-RS 2.0 RI bundle and add all the JAR files contained within to your application’s classpath.
Source
Web Deployment Descriptor (web.xml)
We’re going to modify the web.xml file to declare a Jersey servlet and a corresponding URL mapping, as well as a CORS filter. Add the following to web.xml:
<servlet>
<servlet-name>JAX-RS Servlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.example.rest</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JAX-RS Servlet</servlet-name>
<url-pattern>/jaxrs/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Notes:
- url-pattern: “/jaxrs/*”, will root our web services at the /jaxrs path
- CorsFilter
- Depending on how and where you deploy your back-end application, and what server you use, you will most likely need a variation of these settings to allow your front-end Angular application to access the services exposed by the back end. Essentially, if your client application is running on a different domain, modern browsers will restrict the call made to the domain running your back end, unless the headers in the back end indicate that this call is permitted. For more, read: HTTP access control (CORS)
- The filter provided here is specific to the Apache Tomcat server; for other servers, you will need to enable CORS differently. Note that our cors.allowed.origins parameter is excessively open, so, please, use a more restrictive pattern for your production application.
JPA Classes
In our example, we’re going to be dealing with data from a single table, the “PERSON” table. We already have a Person JPA entity to start with and it’s a POJO with JPA annotations.
Person source:
package com.genuitec.webclipse.example;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Person entity. @author MyEclipse Persistence Tools
*/
@XmlRootElement
@Entity
@Table(name = "PERSON", schema = "STARWARS")
public class Person implements java.io.Serializable {
// Fields
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private String gender;
private String skincolor;
private String haircolor;
private String eyecolor;
private String birthyear;
private Integer height;
private Integer mass;
// Constructors
/** default constructor */
public Person() {
}
/** minimal constructor */
public Person(Integer id) {
this.id = id;
}
/** full constructor */
public Person(Integer id, String name,
String gender, String skincolor, String haircolor,
String eyecolor, String birthyear,
Integer height, Integer mass) {
this.id = id;
this.name = name;
this.gender = gender;
this.skincolor = skincolor;
this.haircolor = haircolor;
this.eyecolor = eyecolor;
this.birthyear = birthyear;
this.height = height;
this.mass = mass;
}
// Property accessors
@Id
@Column(name = "ID")
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name = "NAME", length = 50)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Column(name = "GENDER", length = 20)
public String getGender() {
return this.gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Column(name = "SKINCOLOR", length = 30)
public String getSkincolor() {
return this.skincolor;
}
public void setSkincolor(String skincolor) {
this.skincolor = skincolor;
}
@Column(name = "HAIRCOLOR", length = 30)
public String getHaircolor() {
return this.haircolor;
}
public void setHaircolor(String haircolor) {
this.haircolor = haircolor;
}
@Column(name = "EYECOLOR", length = 30)
public String getEyecolor() {
return this.eyecolor;
}
public void setEyecolor(String eyecolor) {
this.eyecolor = eyecolor;
}
@Column(name = "BIRTHYEAR", length = 20)
public String getBirthyear() {
return this.birthyear;
}
public void setBirthyear(String birthyear) {
this.birthyear = birthyear;
}
@Column(name = "HEIGHT")
public Integer getHeight() {
return this.height;
}
public void setHeight(Integer height) {
this.height = height;
}
@Column(name = "MASS")
public Integer getMass() {
return this.mass;
}
public void setMass(Integer mass) {
this.mass = mass;
}
}
REST Interface
Finally, we come to making our person data accessible via REST services. Here’s the class that exposes the REST services via JAX-RS.
PersonFacadeREST source:
package com.genuitec.webclipse.example.rest;
import java.util.List;
import javax.persistence.EntityManager;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.genuitec.webclipse.example.EntityManagerHelper;
import com.genuitec.webclipse.example.Person;
/**
* @author MyEclipse Web Service Tools
*/
@javax.inject.Singleton
@Path("Person")
public class PersonFacadeREST extends AbstractFacade<Person> {
private EntityManager em;
public PersonFacadeREST() {
super(Person.class);
}
@PUT
@Override
@Consumes({ "application/xml", "application/json" })
public Response edit(Person entity) {
if (entity.getName().length() <= 3) {
return Response.status(Status.CONFLICT).entity("Customer name is too short").type(MediaType.TEXT_PLAIN).build();
}
return super.edit(entity);
}
@DELETE
@Path("remove/{id}")
public Response remove(@PathParam("id") Integer id) {
return super.remove(super.find(id));
}
@GET
@Path("{id}")
@Produces({ "application/json" })
public Person find(@PathParam("id") Integer id) {
return super.find(id);
}
@GET
@Override
@Produces({ "application/json" })
public List<Person> findAll() {
return super.findAll();
}
@GET
@Path("{from}/{to}")
@Produces({ "application/xml", "application/json" })
public List<Person> findRange(@PathParam("from") Integer from,
@PathParam("to") Integer to) {
return super.findRange(new int[] { from, to });
}
@GET
@Path("count")
@Produces("text/plain")
public String countREST() {
return String.valueOf(super.count());
}
@Override
protected EntityManager getEntityManager() {
em = EntityManagerHelper.getEntityManager();
return em;
}
}
A few key annotations and methods:
@Path(“Person”)
public class PersonFacadeREST
Our person services will be available at the JAX-RS service relative path, Person – i.e., jaxrs/Person relative to our application’s webroot.
@GET
@Override
@Produces({ "application/json" })
public List<Person> findAll() {
return super.findAll();
}
If a call is made to jaxrs/Person with the HTTP GET method, a list of all people is returned. The @Produces annotation tells JAX-RS that this method can produce JSON output.
Note: It’s inefficient to be using a method that returns a list of all people in your database, so in a practical application, you would want to limit the amount of data retrieved.
@GET
@Path("{id}")
@Produces({ "application/xml", "application/json" })
public Person find(@PathParam("id") Integer id) {
return super.find(id);
}
At jaxrs/Person/{id} (jaxrs/Person/2 for example) you will get Person object containing the details of the person with the specified Id.
@PUT
@Override
@Consumes({ "application/xml", "application/json" })
public Response edit(Person entity) {
if (entity.getName().length() <= 3) {
return Response.status(Status.CONFLICT).entity("Name is too short").type(MediaType.TEXT_PLAIN).build();
}
return super.edit(entity);
}
A PUT request made to jaxrs/Person with the person data in the request body will modify an existing person.
Note: We’ve added some simple name length validation here to demonstrate how errors from the back end can be handled in your front-end application; normally this type of validation should be done in the front end itself.
Building the Angular 2 Application
Over the next few steps, we’ll cover how to create the Angular application along with Angular Services and Components, as well as how you can run and test it. For convenient access to wizards and views, please, switch to the Angular 2 perspective, if you are not using this perspective already. You can switch using Window > Perspective > Open Perspective > Other > Angular 2 or from the perspective switching toolbar.
Note: Only key concepts and source will be presented, you can import the attached SWAngular application, to directly run the front end.
Create the Project
The first step is to create a new Angular 2 project using the New Angular 2 Project wizard. To get started, select File > New > Angular 2 Project. Type in a name for the project, and then click “Finish” to accept the default settings. We create a new project using the angular-cli.
Create the Person Interface
In our Person interface, we simply define different attributes of the Person we will be displaying and manipulating.
To create this, bring up the context menu on the app folder and select New > Interface. Note that we’ve added a person segment to the path so that we can group all the functionality in this single folder.
Replace the code generated in person.ts with the following:
Person.ts source:
export interface Person {
id: number;
name: string;
gender: string;
skincolor: string;
haircolor: string;
eyecolor: string;
height: number;
mass: number;
birthyear: string;
}
Create the Person Service
The Person service is the crux of our Angular application; this service interacts with the REST services exposed by our Java EE back end, allowing us to ultimately read and write data from/to our database.
To create the service, bring up the context menu on the app/person folder that was created in the previous step and select New > Service. Be sure to expand the Advanced group and select “Do not create the code in its own directory” so that we don’t get another subfolder.
Replace the code generated in person.service.ts with the following:
Person.service.ts source:
import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Person } from './person';
import 'rxjs/add/operator/map';
@Injectable()
export class PersonService {
private baseUrl: string = 'http://localhost:8080/SWBackend/jaxrs';
constructor(private http: Http) {
}
get(id: number): Observable<Person> {
let person$ = this.http
.get(`${this.baseUrl}/Person/${id}`, {headers: this.getHeaders()})
.map(mapPerson);
return person$;
}
getAll(): Observable<Person[]> {
let person$ = this.http
.get(`${this.baseUrl}/Person`, {headers: this.getHeaders()})
.map(mapPersons);
return person$;
}
save(person: Person): Observable<Response> {
console.log('Saving person ' + JSON.stringify(person));
return this.http.put(`${this.baseUrl}/Person`, JSON.stringify(person), {headers: this.getHeaders()});
}
private getHeaders() {
let headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/json');
return headers;
}
}
function mapPersons(response: Response): Person[] {
return response.json().map(toPerson);
}
function mapPerson(response: Response): Person {
return toPerson(response.json());
}
function toPerson(r: any): Person {
return r;
}
Let’s review some key methods in the service:
The get method gets the details of a single Person, getAll returns a list of Persons, and the save method is used to update Person details. Each of these methods makes a simple HTTP call to the REST web services exposed by the back end, and they return an Observable.
An Observable is a basic building block of reactive programming, and basically represents a pushed based collection that can change over any amount time. This helps us asynchronously receive and process data, which is key in any modern web application. For more on Observables, please see here.
We’re also dealing with JSON in all cases, either what is returned by our back end or what is being pushed to it. The toPerson method is responsible for the ultimate conversion of the received JSON to our local Person interface, and in this case, the automatic conversion suits us just fine (we’ve designed both the back end and the front end to use the same field names). In other cases, you might have to write a more detailed conversion method.
Finally, we need to register our service as a provider and we can do that in the app module – app.module.ts. We need to import the service class and list it in the providers array.
Working with Components
Now let’s move on to some visuals! We’re going to create a list of people, a person details view and a view that will allow us to make changes to our chosen person – all with Angular components. An Angular component controls a patch of screen, called a view.
Create the Person List Component
From the app/person context menu, choose New > Component. Again, uncheck the “Do not create the code in its own directory” option (do this for each component you create).
The component is automatically imported and declared in the app module, app.module.ts, so you don’t have to do this yourself.
On clicking finish, we will get the Personlist.component.ts class and the personlist.component.html template class. Replace the generated code with the code below.
Personlist.component.ts source:
import { Component, OnInit } from '@angular/core';
import { PersonService } from './person.service';
import { Person } from './person';
@Component({
selector: 'app-personlist',
templateUrl: './personlist.component.html',
styles: []
})
export class PersonlistComponent implements OnInit {
people: Person[] = [];
constructor(private personService: PersonService) { }
ngOnInit() {
this.personService.getAll().subscribe(p => this.people = p);
}
}
Personlist.component.html source:
<section *ngIf="people" class = "panel-body">
<table class = "table-striped table-bordered table-hover table-condensed">
<tr *ngFor="let person of people">
<td><a href="#" [routerLink]="['/person', person.id]">
{{person.name}} </a> </td>
<td><a href="#" [routerLink]="['/person/edit', person.id]">
Edit </a></td>
</table>
</section>
A few key points to be covered in this component:
In ngOnInit, we subscribe to the Observer returned by the PersonService’s getAll method we discussed earlier. When the list of people is obtained, it will be assigned to the people field of the component – the good part is that this is asynchronous and we won’t be blocked waiting for the list.
The rendering of this component is defined in the personlist.component.html file, where we create a table and simply iterate through the people array to create a row for each person. One column with the name of the person, the link allows viewing the person’s details, the second column contains a link allowing editing. Note that the entire table is contained within a section that has a *ngIf=”people” condition, which ensures that the table will be created only after the list of people is returned by the earlier subscription – exactly what we want, achieved without any JavaScript gymnastics!
We also make use of the routerLink directive to create links to other components, we’ll cover that in the section on routers below.
Create the Person Edit Component
Perform similar steps as above to create a component which will be used to perform some basic editing of our people. Replace generated code with the code below.
Personedit.component.ts source:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Person } from './person';
import { PersonService } from './person.service';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-personedit',
templateUrl: './personedit.component.html',
styles: []
})
export class PersoneditComponent implements OnInit, OnDestroy {
person: Person;
sub: any;
errorMessage: string = '';
response: any;
constructor(private personService: PersonService,
private route: ActivatedRoute,
private router: Router) { }
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = Number.parseInt(params['id']);
console.log('getting person with id: ' + id);
this.personService
.get(id)
.subscribe(p => this.person = p);
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
savePersonDetails() {
this.personService.save(this.person).subscribe(p => this.response = p,
e => this.errorMessage = e._body,
() => this.router.navigate(['/person/', this.person.id]));
}
}
Personedit.component.html source:
<section *ngIf="person" class = "panel">
<form (ngSubmit)="savePersonDetails()" #personForm="ngForm" class="panel-body">
<div class="input-group">
<span class="input-group-addon">Name</span>
<input type="text" class = "form-control" name="personname" required [(ngModel)]="person.name" #personname="ngModel">
</div>
<div [hidden]="personname.valid || personname.pristine" class="error">
Name cannot be empty.
</div>
<br/>
<div class="input-group">
<span class="input-group-addon">Credit limit</span>
<input type="number" class ="form-control" name="creditlimit" required [(ngModel)]="person.height">
</div>
<br/>
<button type="submit" [disabled]="!personForm.form.valid" class = "btn">Save</button>
</form>
</section>
<section *ngIf="errorMessage" class = "error">{{errorMessage}}</section>
Key points:
In our component’s class, notice the subscription to the route’s parameter object to get us the Id of the person being edited. We’ll cover routes in more detail later.
We’re using Angular’s bi-directional databinding capability to map fields in our class to HTML elements. This allows model data to be displayed in the field, and changes to be propagated back to the model automatically. Furthermore, we’re performing some local validation by creating a local template variable (#personname) setting to ngModel. We can then check whether there are problems with that particular field, and if there are, we display the “error” div.
We’re setting another local template variable (#personform) to the ngForm directive. This allows us to disable submission of any of the fields contains invalid data.
On submission, we call the savePersonDetails method back in the component.
Create the Person Details Component
Persondetails.component.ts source:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Person } from './person';
import { PersonService } from './person.service';
@Component({
selector: 'app-persondetail',
templateUrl: './persondetail.component.html',
styles: []
})
export class PersondetailComponent implements OnInit, OnDestroy {
person: Person;
sub: any;
constructor(private personService: PersonService,
private route: ActivatedRoute,
private router: Router) { }
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = Number.parseInt(params['id']);
this.personService
.get(id)
.subscribe(p => this.person = p);
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
gotoEdit() {
this.router.navigate(['/person/edit', this.person.id]);
}
}
Persondetails.component.html source:
<section *ngIf="person" class = "panel">
<form (ngSubmit)="savePersonDetails()" #personForm="ngForm" class="panel-body">
<div class="input-group">
<span class="input-group-addon">Name</span>
<input type="text" class = "form-control" name="personname" required [(ngModel)]="person.name" #personname="ngModel">
</div>
<div [hidden]="personname.valid || personname.pristine" class="error">
Name cannot be empty.
</div>
<br/>
<div class="input-group">
<span class="input-group-addon">Credit limit</span>
<input type="number" class ="form-control" name="creditlimit" required [(ngModel)]="person.height">
</div>
<br/>
<button type="submit" [disabled]="!personForm.form.valid" class = "btn">Save</button>
</form>
</section>
<section *ngIf="errorMessage" class = "error">{{errorMessage}}</section>
We won’t go into too many details here, as this component is quite similar to the others. Observe how, in the template, we access the gender field to decide on a class to be used for the icon.
Setting up Routes
So we’ve created a number of components, but we haven’t actually wired them together yet. How would a user get to these views? That’s where routing comes in.
Let’s jump right ahead and look at routes we define in the app.routes.ts file.
App.routes.ts source:
import { Routes, RouterModule } from '@angular/router';
import { PersonlistComponent } from './person/personlist.component';
import { PersondetailComponent } from './person/persondetail.component';
import { PersoneditComponent } from './person/personedit.component';
// Route config let's you map routes to components
const routes: Routes = [
// map '/persons' to the people list component
{
path: 'people',
component: PersonlistComponent
},
// map '/persons/:id' to person details component
{
path: 'person/:id',
component: PersondetailComponent
},
{
path: 'person/edit/:id',
component: PersoneditComponent
},
// map '/' to '/persons' as our default route
{
path: '',
redirectTo: '/people',
pathMatch: 'full'
},
];
export const routing = RouterModule.forRoot(routes);
The routes array we create is pretty straightforward. For a given app relative path, we specify the component to be loaded. We can also set up redirects to other paths within the application. Finally, we export our routes using RouterModule#forRoot. To ensure these routes are actually respected, we import this “routing” in the app module.
App.module.ts source:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { routing } from './app.routes';
import { AppComponent } from './app.component';
import { PersonService } from './person/person.service';
import { PersonlistComponent } from './person/personlist.component';
import { PersondetailComponent } from './person/persondetail.component';
import { PersoneditComponent } from './person/personedit.component';
@NgModule({
declarations: [
AppComponent,
PersonlistComponent,
PersondetailComponent,
PersoneditComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
routing
],
providers: [PersonService],
bootstrap: [AppComponent]
})
export class AppModule { }
Recall in personlist.component.html, we had used a routerlink directive:
`<a href=”#” [routerLink]=”[‘/person/edit’, person.id]”>Edit<a/>`
This directive binds clickable HTML elements to a route, giving the router control over these elements. This allows us to create links in our application without crafting URLs manually.
Running Your Application
Deploy the back-end application
Ensure that your back-end application is deployed and running first. If you are using the attached back-end project, SWBackend, import it from the archive and use Run As > Maven install to ensure that the dependencies are downloaded into your project prior to deployment. In MyEclipse, this step is not necessary, but you may need to wait for the build process to complete to ensure all the dependencies are downloaded.
You can now deploy your application using Run As > Run on Server / MyEclipse Server Application to deploy the back end to your preferred Java EE server.
To test that the back end is running correctly, see that this URL: http://localhost:8080/SWBackend/jaxrs/Person/1 returns JSON data for Luke Skywalker. If you don’t see the output or you see errors in the console, try to resolve those before moving to the next step. Please, remember to ensure that dependencies are downloaded and the project has been fully built.
Deploy the front-end application
Import the front-end project, SWAngular and choose Run As > npm Install to ensure all the Node dependencies are downloaded into the project. You should see the npm installation process in the Terminal+ view.
You can now go ahead and deploy your application, from the project’s context menu, choose Run As > Angular 2 Web Application or you can even start the application from the Servers view – SWAngular will be listed under the Angular CLI node. Again, in the Terminal+ view, you should see the output of the “ng serve” process that is deploying your application.
If you used the Run As action, chrome will automatically open with the deployed application – if not, open a browser and navigate to http://localhost:4200/ to see the front end in action.
People List
Details for Anakin Skywalker
Editing details for Anakin Skywalker
Final Thoughts
With frameworks like Angular 2, we’re able to move away from what we like to call “back-end driven” front-end applications. As you have seen, we’re able to create a front end which takes advantage of improved browser technology and provides modern interaction patterns. All this without having to write special code outside of what a framework like Struts or JSF would normally be capable of, while still maintaining a strong link with the back-end.
Attached Projects
SWBackend.zip – Backend JAX-RS / JPA Java EE Project
SWAngular.zip – Frontend Angular 2 Project
References