Jakarta EE 10 Application with an Angular Frontend

In this tutorial, we’ll walk through the process of creating a simple Jakarta EE 10 application that exposes a RESTful endpoint to fetch a list of customers. We’ll then build an Angular front end that consumes this endpoint and displays the list of customers in a user-friendly format.

Prerequisites

Before starting, ensure you have the following tools installed:

  • Java Development Kit (JDK) 11 or later
  • Maven (for building the Jakarta EE application)
  • WildFly or GlassFish (for deploying the Jakarta EE application)
  • Node.js and npm (for managing Angular and its dependencies)
  • Angular CLI (for creating and managing Angular projects)

Coding the Jakarta EE back-end

The source code for our application is a Domain-Driven-Model which exposes an Endpoint with CRUD operations against a single Entity, If you are new to JAX-RS, you can find more details about how to build this example here: REST CRUD application with JPA | A complete example

Her,e we will show the core Components of our example. Firstly, the Customer Entity:

@Entity
@NamedQuery(name = "Customers.findAll",
        query = "SELECT c FROM Customer c ORDER BY c.id")
public class Customer {
    @Id
    @SequenceGenerator(
            name = "customerSequence",
            sequenceName = "customerId_seq",
            allocationSize = 1,
            initialValue = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customerSequence")
    private Long id;

    @Column
    private String firstName;

    @Column
    private String lastName;
	
    @Column
    private String email;

    // Getters/Setters omitted for brevity

}

Our back-end application uses the Repository pattern to manage Database access. The following CustomerRepository Bean controls the access to the Customer Entity:

@ApplicationScoped
public class CustomerRepository {

    @PersistenceContext
    private EntityManager entityManager;

    public List<Customer> findAll() {
        return entityManager.createNamedQuery("Customers.findAll", Customer.class)
                .getResultList();
    }

    public Customer findCustomerById(Long id) {

        Customer customer = entityManager.find(Customer.class, id);

        if (customer == null) {
            throw new WebApplicationException("Customer with id of " + id + " does not exist.", 404);
        }
        return customer;
    }
    @Transactional
    public void updateCustomer(long id, Customer customer) {
        Customer c = entityManager.find(Customer.class, id);
        c.setFirstName(customer.getFirstName());
        c.setLastName(customer.getFirstName());
        c.setEmail(customer.getEmail());
        entityManager.persist(c);

    }
    @Transactional
    public void createCustomer(Customer customer) {

        entityManager.persist(customer);

    }
    @Transactional
    public void deleteCustomer(Long customerId) {

        Customer c = findCustomerById(customerId);
        entityManager.remove(c);
    }
}

Finally, each Repository method is wrapped by the corresponding endpoint methods in the CustomerEndpoint Class:

@Path("customer")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")
public class CustomerEndpoint {

    @Inject CustomerRepository customerRepository;

    @GET
    public List<Customer> getAll() {
        return customerRepository.findAll();
    }

    @POST
    public Response create(Customer customer) {
        customerRepository.createCustomer(customer);
        return Response.status(201).build();
    }

    @GET
    @Path("/{id}")
    public Response findById(@PathParam("id") long id) {
        Customer customer = customerRepository.findCustomerById(id);
        return Response.ok(customer).build();
    }

    @PUT
    @Path("/{id}")
    public Response updateCustomer(@PathParam("id") int id, Customer customer) {

        customerRepository.updateCustomer(id, customer);
        return Response.ok(customer).build();
    }

    @DELETE
    @Path("/{id}")
    public Response deleteUser(@PathParam("id") long id) {
        customerRepository.deleteCustomer(id);
        return Response.noContent().build();
    }

}

The source code of the back-end also includes the JaxRsActivator and the Maven WildFly plugin to deploy the application on WildFly with:

mvn install wildfly:deploy

Finally, we need to enable CORS on WildFly. Enabling CORS (Cross-Origin Resource Sharing) is necessary when you want to allow your web application, running on a different domain or port, to make HTTP requests to your WildFly server. For this purpose, you can follow the instructions available in this article: How to configure CORS on WildFly

Coding the Angular Frontend

Angular is a popular open-source web application framework. It allows building dynamic, modern web applications with a strong focus on a component-based architecture, two-way data binding, and modularity. Angular allows developers to create Single Page Applications (SPAs) that are responsive, fast, and easy to maintain.

Angular CLI (Command Line Interface) is a powerful tool for automating various tasks in Angular development. It provides developers with commands to quickly generate, build, test, and deploy Angular applications, ensuring consistency and best practices across the project.

To get started with Angular, firstly make sure you have the npm and Node.js available your system. The following link will guide you through the installation: Downloading and installing Node.js and npm | npm Docs (npmjs.com)

Next, we will install the Angular CLI by typing the following command:

npm install -g @angular/cli

The Angular CLI allows you to use the ng command to scaffold, build and test your applications. The first command we will learn is ng new which creates a new Angular application.

>ng new angular-app
? Which stylesheet format would you like to use? CSS             [ https://developer.mozilla.org/docs/Web/CSS   ]
? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? no
. . . . .
⠴ Installing packages (npm)...                     

In our example, we are creating an Angular project “angular-app” which relies on Mozilla’s CSS. SSR and SSG are valuable options if your Angular application needs faster initial load times and better SEO. However, for our basic example we will not add them.

Building the Angular Model and Service

Angular apps are built using Components, which are reusable, self-contained units of UI and logic. Before creating the Components, we need to define the Model which matches the Customer Entity. Also, we need a Service to interact with the back-end. Let’s begin with the Model.

In Angular, you can define models in TypeScript, which is a superset of Javascript. From the Command Line, create the Customer Model with the following command:

ng g class Customer --type=model

Then, edit the file customer.model.ts to include the fields of the Customer Entity:

export class Customer {
    id!: number;
    firstName!: string;
    lastName!: string;
    email!: string;

}

To interact with the back-end, we will need an Angular Service which maps the Endpoints available in our WildFly REST Service. You can generate an Angular Service with the following command:

ng g s services/customer

Then, edit the services/customer.service.ts file to include the following TypeScript Service:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Customer } from '../customer.model';

@Injectable({
  providedIn: 'root'
})
export class CustomerService {

  private basUrl = "http://localhost:8080/jaxrs-demo/customer"

  constructor(private httpClient: HttpClient) {

  }

  getCustomerList(): Observable<Customer[]> {
    return this.httpClient.get<Customer[]>(`${this.basUrl}`);
  }

  createCustomer(customer: Customer): Observable<Object> {
     console.log('Sending customer data:', customer);
     return this.httpClient.post(`${this.basUrl}`, customer);
  }

  getCustomerById(id: number): Observable<Customer>{
    return this.httpClient.get<Customer>(`${this.basUrl}/${id}`);
  }

  updateCustomer(id:number, customer:Customer): Observable<Object>{
    return this.httpClient.put(`${this.basUrl}/${id}`, customer);
  }

  deleteCustomer(id:number): Observable<Object>{
    return this.httpClient.delete(`${this.basUrl}/${id}`);
  }
}

This Service uses Angular’s HttpClient to interact with a backend API at http://localhost:8080/jaxrs-demo/customer, providing methods to get, create, update, and delete customer records. This service abstracts the API communication, making it easy for components to use these methods to interact with the backend without directly handling HTTP logic.

Creating the Angular Components

Next, we will build our Angular Components which contain the Views our application uses to interact with the server side application. A typical Angular front end, requires a set of three views:

ng g c customer-list

ng g c customer-create

ng g c customer-update

The first command, will create the customer-list Component which displays the list of Customers in tabular format. The customer-create contains the Form to insert a new Customer. Finally, the customer-update also contains a Form which allows updating an existing Customer. All three commands will generate the corresponding .ts, .html, .css, and .spec.ts files.

Once that you have the backbone for all components, let’s start the customization.

Firstly, let’s edit the customer-list Component, which relies on the customer.service to fetch data from the back-end and uses the customer.model to visualize them:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { CustomerService } from '../services/customer.service';
import { Customer } from '../customer.model';

@Component({
  selector: 'app-customer-list',
  templateUrl: './customer-list.component.html',
  styleUrls: ['./customer-list.component.css']
})
export class CustomerListComponent implements OnInit {

  customers: Customer[] | undefined;

  constructor(private customerService: CustomerService, private router: Router) {

  }

  ngOnInit(): void {
    this.getCustomers();
  }

  private getCustomers() {
    this.customerService.getCustomerList().subscribe(data => {
      this.customers = data;
    });
  }

  updateCustomer(id: number) {
    this.router.navigate(['update-customer', id]);
  }

  deleteCustomer(id: number) {
    this.customerService.deleteCustomer(id).subscribe(data => {
      console.log(data);
      this.getCustomers();
    });
  }
}

Then, let’s edit the customer-create Component as well:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { CustomerService } from '../services/customer.service';
import { Customer } from '../customer.model';

@Component({
  selector: 'app-customer-create',
  templateUrl: './customer-create.component.html',
  styleUrls: ['./customer-create.component.css']
})
export class CustomerCreateComponent implements OnInit {

  customer: Customer = new Customer();

  constructor(private customerService: CustomerService, private router: Router) { }

  ngOnInit(): void {
  }

  saveCustomer() {
    this.customerService.createCustomer(this.customer).subscribe({
      next: (data) => {
        console.log(data);
        this.redirectToCustomerList();
      },
      error: (e) => {
        console.log(e);
      }
    });
  }

  redirectToCustomerList() {
    this.router.navigate(['/customers']);
  }

  onSubmit() {
    console.log(this.customer);
    this.saveCustomer();
  }
}

This Angular component is responsible for creating a new customer. It includes a form where a user can input customer details. When the user submits the form (onSubmit()), the component calls saveCustomer(), which uses the CustomerService to send the customer data to the backend API. Upon a successful response, the component redirects the user to the customer list page. The ngOnInit() method is included but doesn’t perform any actions, serving as a placeholder for future initialization logic.

Finally, the customer-update Component which is responsible to update an existing Customer:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { CustomerService } from '../services/customer.service';
import { Customer } from '../customer.model';

@Component({
  selector: 'app-customer-update',
  templateUrl: './customer-update.component.html',
  styleUrls: ['./customer-update.component.css']
})
export class CustomerUpdateComponent implements OnInit {
  id!: number;
  customer: Customer = new Customer();

  constructor(private customerService: CustomerService,
    private route: ActivatedRoute, private router: Router) { }

  private getCustomerById() {
    this.id = this.route.snapshot.params['id'];
    this.customerService.getCustomerById(this.id).subscribe({
      next: (data) => {
        this.customer = data;
      },
      error: (e) => {
        console.log(e);
      }
    });
  }
  ngOnInit(): void {
    this.getCustomerById();
  }
  updateCustomer() {
    this.customerService.updateCustomer(this.id, this.customer).subscribe({
      next: (data) => {
        console.log(data);
        this.redirectToCustomerList();
      },
      error: (e) => {
        console.log(e);
      }
    });
  }
  redirectToCustomerList() {
    this.router.navigate(['/customers']);
  }
  onSubmit() {
    console.log(this.customer);
    this.updateCustomer();
  }
}

As you can see, the main difference with the CustomerCreateComponent is that the CustomerUpdateComponent performs a getCustomerById() to retrieve the Customer object. The Component will use the Customer id in the updateCustomer which updates the existing Customer.

Finally, to map the URL of our Angular Components, we will use the AppRoutingModule which allows for navigation between different parts of the application based on the URL: CustomerListComponent for displaying the list of customers, CustomerCreateComponent for creating a new customer, and CustomerUpdateComponent for updating an existing customer. The default route ('') redirects to the customer list, ensuring that the CustomerListComponent is the landing page when the application starts.

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CustomerCreateComponent } from './customer-create/customer-create.component';
import { CustomerListComponent } from './customer-list/customer-list.component';
import { CustomerUpdateComponent } from './customer-update/customer-update.component';

const routes: Routes = [
  { path: 'customers', component: CustomerListComponent },
  { path: '', redirectTo: 'customers', pathMatch: 'full' },
  { path: 'create-customer', component: CustomerCreateComponent },
  { path: 'update-customer/:id', component: CustomerUpdateComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Building an running the Angular Application

Assuming you have downloaded the source code for this example from GitHub, before running it the first time you should first run an npm update command:

npm update

The command npm update updates the dependencies in your project to the latest versions that satisfy the version ranges specified in your package.json file.

Then, you are ready to serve the Angular application with:

ng serve --o

By default, the browser will disaply the application on localhost:4200 .As you can see from the landing page, the Customer List will display some customers loaded with the import.sql script on the server-side.

angular tutorial with jakartaee

By clicking on the Create Customer upper link, you will be able to insert a new Customer object in the Database:

Finally, by clicking on the Update and Delete buttons from the Customer List you will be able to perform the specific actions on the Customer Entity.

Conclusion

In this tutorial, we created a simple Jakarta EE 10 application that exposes a RESTful endpoint and an Angular front end that consumes this endpoint. This setup provides a basic yet powerful foundation for building more complex enterprise applications with a modern front end and a robust backend.

Source code: https://github.com/fmarchioni/mastertheboss/tree/master/jax-rs/angular