User management with Quarkus and Angular on Kubernetes - Part 2

The second part of our User Management tutorial with an Nx Angular Frontend

In the second part of our tutorial, we wil focus on the UI part of our User management Application and consume the exposed services from part 1 through an Angular application.

Project setup


Nx Angular

Nx is a smart and extensible build framework that helps you develop, test, build, and scale Angular applications with fully integrated support for modern tools like Jest, Cypress, Storybook, ESLint, NgRx, and more. Nx takes the core of the Angular framework and the CLI and provides a more modern development experience on top of them. Nx provides better linting, better testing, a faster CLI, support for popular community libraries and tools.

I chose Nx Angular for the development of our UI for the following reasons:

  • Nx provides a monorepo structure and a nice way to share code between multiple applications
  • The included CLI is powerful and easy to use
  • Nx comes with tools like Jest, Cypress, ESLInt and Storybook to help us work more effectively and efficiently

Create Nx workspace

Make sure you have Node.js installed on your machine.

Install Nx with npm:

npm install -g nx

Let’s create a new Nx workspace with an Angular Application with the following command:

npx create-nx-workspace --preset=angular

Choose a workspace name, an application name and choose Sass as a stylesheet format.

After a while the project should be ready to start. Run the following command to run the application and verify that the workspace creation was successful:

nx serve

Install required libraries

Bootstrap

Building a nice UI from scratch is not an easy task. Therefore, we will use Bootstrap to support us. Bootstrap is a powerful collection of HTML, CSS, and JavaScript tools for creating and building web pages and web applications.

Let’s install the toolkit by executing the following command:

npm i bootstrap

In addition to Bootstrap, we will need the types definition to integrate some Bootstrap components with angular

nx add @types/bootstrap

Ngx-Bootstrap

For our UI, we will need some specific functionalities like Modal Popups for instance. Those components are included in Bootstrap but need some groundwork to be fully integrated with Angular. Ngx-Bootstrap provides those kinds of components and enables an easy integration of Bootstrap components in an Angular Application.

nx add ngx-bootstrap@next

Fontawesome

Fontawesome is a set of icons providing the end user a more friendly user experience.

nx add @fortawesome/fontawesome-free

After the installation of Fontawesome, we need to specify the javascript file to our application. Add the following line of code into the angular.json file.

"scripts": [
  "node_modules/@fortawesome/fontawesome-free/js/all.js"
]

Create the Application

Client services

User model

We will start by creating the user class in typescript. This will be the model representation of our exposed REST service. Create a new file called User.ts into src/app/model and copy the following content:

export class UserModel {
  public id: number;
  public firstname: string;
  public lastname: string;
  public birthdate: Date;

  constructor() {
  }
}

User service

Let’s create an angular service to call our exposed REST services. Execute the following command:

ng g s service/usermgmt

This will generate 2 new files into the package src/app/service:

  • the actual service
  • a test class for our service

Copy the following content in the generated service:

import {Injectable} from '@angular/core';
import {environment} from "../../environments/environment";
import {Observable} from "rxjs";
import {HttpClient} from "@angular/common/http";
import {UserModel} from "../model/User";

@Injectable({
  providedIn: 'root'
})
export class UsermgmtService {
  private API = environment.apiUrl + 'api/user/management';

  constructor(private http: HttpClient) {
  }

  findAll(): Observable<any> {
    return this.http.get(this.API + '/all');
  }

  get(id: string): Observable<any> {
    return this.http.get(this.API + '/' + id);
  }

  delete(id: number): Observable<any> {
    return this.http.delete(this.API + '/' + id);
  }

  save(model: UserModel): Observable<any> {
    return this.http.post(this.API + '/save', model);
  }
}

In that file, you can recognize the REST methods of our Quarkus service. To call our Quarkus service, we need to specify the address of our Quarkus backend. For that, we will use the environment mechanism of Angular. Add the following constant into src/environments/environment.ts.

apiUrl: 'http://localhost:8080/'

List of users

Now that the service has been created, we are ready to call the list of user and to render them. Execute the following command to create a new Angular component named list:

nx g c list

The CLI will create 4 files for you: the html file, the Sass file, the typescript file and a test file.

Copy the following logic into the typescript file:

export class ListComponent implements OnInit {

  users: UserModel[] = [];
  bsModalRef: BsModalRef;

  constructor(private service: UsermgmtService,
              private modalService: BsModalService) { }

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

  delete(user: UserModel) {
    const initialState = {
      text: 'Do you really want to delete ' + user.lastname + ', ' + user.firstname + '?',
    };

    // @ts-ignore
    this.bsModalRef = this.modalService.show(DeleteModalComponent, {initialState});
    this.bsModalRef.content.event.subscribe(() => {
      this.service.delete(user.id).subscribe(result => {
        this.users = result;
        this.loadUsers();
      });
    });
  }

  private loadUsers() {
    this.service.findAll()
      .subscribe(result => {
        this.users = result;
      });
  }
}

The loadUsers method is called after the component initialization and loads all our users by calling the REST service in the backend. The delete method shows a confirmation dialog and deletes the selected user. Have a look at the source code of theDeleteModalComponent for more details on how to create the Modal Popup with Bootstrap.

Now copy the html content.

<button class="btn btn-outline-primary btn-xl mb-3" [routerLink]="['../create']" aria-label="New">
  <i class="fa fa-plus"></i>
  New
</button>
<div *ngFor="let item of users;" class="card-header container-fluid bg-white p-0">
  <div class="row">
    <div class="col">
      <h5 class="card-body text-muted mb-2 pl-0">{{item.lastname}}, {{item.firstname}}</h5>
    </div>
    <div class="col-auto">
      <button type="button" class="btn btn-outline-primary btn-circle btn-lg m-1" [routerLink]="['../edit', item.id]" aria-label="Edit">
        <i class="fa fa-edit"></i>
      </button>
      <button type="button" class="btn btn-outline-primary btn-circle btn-lg m-1" (click)="delete(item)" aria-label="Delete">
        <i class="fa fa-trash"></i>
      </button>
    </div>
  </div>
</div>

You should now see our 3 users in your browser.

List of users

Edit a user

We can now see our users. Let’s add the edit functionality. Create a new component called edit.

nx g c edit

Copy the following code into edit.component.ts

export class EditComponent implements OnInit {
  model: UserModel = new UserModel()

  constructor(private route: ActivatedRoute,
              private notificationService: NotificationService,
              private userMgmtService: UsermgmtService) {
  }

  ngOnInit(): void {
    let id = this.route.snapshot.paramMap.get('id');

    if (id !== null) {
      this.userMgmtService.get(id)
        .subscribe(result => {
          this.model = result;
        });
    }
  }

  submit() {
    this.userMgmtService.save(this.model)
      .subscribe(result => {
        this.notificationService.notifySuccess('The user has been saved successfully');
      });
  }
}

The id of the user will be passed in the url and used after the component initialization to call the corresponding angular service and fetch the data of the user from our REST service. We call the save method when the form is submitted. The notificationService will show a Toast with a success message if everything went well. Have a look at the source code of the NotificationService for more details about the Toast functionality.

The edit.component.html will look like this:

<form (ngSubmit)="submit()">
  <div class="m-3">
    <div class="form-floating row mb-3">
      <input type="text" class="form-control" id="firstname" name="firstname" [(ngModel)]="model.firstname">
      <label for="firstname">Firstname</label>
    </div>
    <div class="form-floating row mb-3">
      <input type="text" class="form-control" id="lastname" name="lastname" [(ngModel)]="model.lastname">
      <label for="lastname">Lastname</label>
    </div>
    <div class="form-floating row mb-3">
      <input type="date" class="form-control date-input" id="birthdate" name="birthdate" [(ngModel)]="model.birthdate">
      <label for="birthdate">Birthdate</label>
    </div>
    <div class="d-grid gap-2 d-md-flex justify-content-md-start">
      <button class="btn btn-outline-primary btn-lg" [routerLink]="['/list']" aria-label="Back">
        <i class="fa fa-arrow-left"></i>
        Back
      </button>
      <button type="submit" class="btn-primary btn-lg" aria-label="Save">
        <i class="fa fa-save"></i>
        Save
      </button>
    </div>
  </div>
</form>

To be able to navigate between our 2 components, we need to configure the Angular Router. Execute the following command to create a Routing Module.

nx generate module app-routing

Now paste the following content:

const routes: Routes = [
  { path: '', redirectTo: '/list', pathMatch: 'full' },
  { path: 'edit/:id', component: EditComponent },
  { path: 'list', component: ListComponent },
  { path: 'create', component: EditComponent}
];

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

Go to your browser. You should now be able to select, edit or delete a user from the list.

Edit user

Build the client with Gradle

In order to include the Angular application in the Gradle build process, we are going to use the gradle-node-plugin This will enable us to call a npm-task during the build process. Create a new file called build.gradle at the root of the client project and copy the following content:

plugins {
  id "com.github.node-gradle.node" version "${nodeGradlePluginVersion}"
}

task buildClient(type: NpmTask) {
  dependsOn npmInstall
  args = ['run', 'build', 'usermgmt-client', '--prod']
}

build.dependsOn buildClient

// Clean task
task removeDist(type: Delete) {
  delete "dist"
}
clean.dependsOn removeDist

This will execute the command npm run build usermgmt-client --prod during the gradle build-phase and therefore create the bundle files for production.

Create a Docker Container

To dockerize our Application we will use a NGINX server. For this, we need to create a configuration file for nginx. Create a file inside the client folder and name it nginx.config.

server {
    listen   80;
    server_name client;
    root /usr/share/nginx/html;

    location /usermgmt/ {
      try_files $uri$args $uri$args/ /usermgmt/index.html;
    }
}

Add the Dockerfile inside the client folder.

FROM nginx:latest
COPY nginx.config /etc/nginx/conf.d/default.conf
COPY /dist/apps/usermgmt-client /usr/share/nginx/html/usermgmt

Now add the following lines to the docker-compose.yml file of the main folder.

usermgmt-client:
  image: client
  build:
    context: ./client
  ports:
    - "4200:80"

You should now be able to run the whole application in a Docker Container. Let’s rebuild our application and start it up by running:

$ gradle clean build && docker-compose up -d --build

Now go to your browser with the following url: http://localhost:4200/usermgmt.

Summary

In the second part of that tutorial, we created the Angular Client of our User Management Application with Nx. We are now able to run the application and its services inside a Container. In the next step, we will configure the application to deploy it to a Kubernetes cluster.

The source code of the project can be found in github.