Web Client Application

Bootstrap the frontend application

In this section, you will use Angular 8 to create the frontend application project. If you do not have Angular CLI installed on your machine, open a terminal window and run npm install -g @angular/cli@8. This will install the version 8 of Angular.

To create the frontend application

  1. In a terminal, navigate to the same root directory as the backend application directory.
  2. Run ng new events-ui and accept the defaults. This creates the frontend project directory named events-ui and installs Angular8 dependencies.
  3. Open the project directory in your favorite frontend IDE.
  4. Create or edit the following folders and files in the project directory:
    • /src/app/model directory - to create
    • /src/environments directory - to edit
    • /src/app/component directory - to create
    • /src/app/app.module.ts file - to edit

Model

To implement the model

  1. Create a directory named model
  2. In the model directory create the event.ts file which defines the event received on the web-socket.
  3. Write the following code for the event class:
export class Event {
    tenant: string;
    eventTime: string;
}

Environment

The environments directory is created by default at the ng new command. It contains 2 files:

  • environment.ts - the default configuration file with the default environment settings
  • environment.prod.ts - the production specific configuration file

To edit the environment.ts file

  1. Open environment.ts and copy the following code in it:
export const environment = {
  production: false,
  apiEndpoint: '<%WEBSOCKET-URL%>'
};
  1. Replace <%WEBSOCKET-URL%> with the public web socket endpoint of the events-application server app. For example http://localhost:8080/socket, if the app is running on localhost.

Component

To implement the component directory

  1. Create a component directory named component
  2. By default, Angular8 creates component files. Their names start with app.component. Move the following types of component files to the previously created directory:
  • app.component.ts - which stores the service that initiates web-socket and receives events on it
  • app.component.html - which stores the display of the component
  • app.component.css - which stores the style for the component
  1. Write the following code for the app.component.ts:
@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css'],
  providers: [],
})
export class EventComponent implements OnInit {

  event: Event;
  _eventSubscription: ISubscription;

  serverUrl = environment.apiEndpoint;
  stompClient: any;

  connect() {
    const socket = new SockJS(this.serverUrl);
    this.stompClient = Stomp.over(socket);

    const _this = this;
    this.stompClient.connect({}, function (frame: any) {
      console.log('Connected: ' + frame);

      _this.stompClient.subscribe('/clock/event', function (data: any) {
        _this.event = JSON.parse(data.body);
      });
    });
  }

  constructor() { }

  ngOnInit() {
    this.event = new Event();
    this.event.tenant = 'N/A';
    this.event.eventTime = 'N/A';
    this.connect();
  }

  ngOnDestroy() {
    this._eventSubscription.unsubscribe();
  }

}
  1. Import the required packages for app.component.ts above the class declaration.
import { Component, OnInit } from '@angular/core';
import { Event } from '../model/event';
import { ISubscription } from 'rxjs/Subscription';
import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';
import { environment } from '../../environments/environment';
  1. Write the following code for the app.component.html:
<div class="event-container">
  <mat-form-field>
    <input matInput [disabled]="true" placeholder="Tenant" value="{{ event.tenant }}">
  </mat-form-field>
  <mat-form-field>
    <input matInput [disabled]="true" placeholder="EventTime" value="{{ event.eventTime }}">
  </mat-form-field>
</div>
  1. Write the following code for the app.component.css:
.event-container {
  display: flex;
  flex-direction: column;
}

.event-container > * {
  width: 100%;
}

Application Module

The module file stores the links to other modules and the imports for the frontend application.

To edit the app.module.ts file

  1. Open the typescript file named app.module.ts
  2. Write the following code for the app.module.ts
@NgModule({
  exports: [
    A11yModule,
    CdkStepperModule,
    CdkTableModule,
    CdkTreeModule,
    DragDropModule,
    MatAutocompleteModule,
    MatBadgeModule,
    MatBottomSheetModule,
    MatButtonModule,
    MatButtonToggleModule,
    MatCardModule,
    MatCheckboxModule,
    MatChipsModule,
    MatStepperModule,
    MatDatepickerModule,
    MatDialogModule,
    MatDividerModule,
    MatExpansionModule,
    MatGridListModule,
    MatIconModule,
    MatInputModule,
    MatListModule,
    MatMenuModule,
    MatNativeDateModule,
    MatPaginatorModule,
    MatProgressBarModule,
    MatProgressSpinnerModule,
    MatRadioModule,
    MatRippleModule,
    MatSelectModule,
    MatSidenavModule,
    MatSliderModule,
    MatSlideToggleModule,
    MatSnackBarModule,
    MatSortModule,
    MatTableModule,
    MatTabsModule,
    MatToolbarModule,
    MatTooltipModule,
    MatTreeModule,
    PortalModule,
    ScrollingModule,
  ]
})
export class DemoMaterialModule {}
  1. Import all the required libraries and modules:
import {NgModule} from '@angular/core';
import {A11yModule} from '@angular/cdk/a11y';
import {DragDropModule} from '@angular/cdk/drag-drop';
import {PortalModule} from '@angular/cdk/portal';
import {ScrollingModule} from '@angular/cdk/scrolling';
import {CdkStepperModule} from '@angular/cdk/stepper';
import {CdkTableModule} from '@angular/cdk/table';
import {CdkTreeModule} from '@angular/cdk/tree';
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatBadgeModule} from '@angular/material/badge';
import {MatBottomSheetModule} from '@angular/material/bottom-sheet';
import {MatButtonModule} from '@angular/material/button';
import {MatButtonToggleModule} from '@angular/material/button-toggle';
import {MatCardModule} from '@angular/material/card';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatChipsModule} from '@angular/material/chips';
import {MatStepperModule} from '@angular/material/stepper';
import {MatDatepickerModule} from '@angular/material/datepicker';
import {MatDialogModule} from '@angular/material/dialog';
import {MatDividerModule} from '@angular/material/divider';
import {MatExpansionModule} from '@angular/material/expansion';
import {MatGridListModule} from '@angular/material/grid-list';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatListModule} from '@angular/material/list';
import {MatMenuModule} from '@angular/material/menu';
import {MatNativeDateModule, MatRippleModule} from '@angular/material/core';
import {MatPaginatorModule} from '@angular/material/paginator';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MatRadioModule} from '@angular/material/radio';
import {MatSelectModule} from '@angular/material/select';
import {MatSidenavModule} from '@angular/material/sidenav';
import {MatSliderModule} from '@angular/material/slider';
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatSortModule} from '@angular/material/sort';
import {MatTableModule} from '@angular/material/table';
import {MatTabsModule} from '@angular/material/tabs';
import {MatToolbarModule} from '@angular/material/toolbar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {MatTreeModule} from '@angular/material/tree';

Edit Other Angular Default Files

For the frontend application to work, you need to edit some of the default angular files, created by the ng new name-of-the-app command.

Find the files that need editing in the src/ directory:

  • main.ts - the main entry point for your app. Compiles the application with the JIT compiler and bootstraps the application’s root module AppModule to run in the browser.
import './polyfills';

import {HttpClientModule} from '@angular/common/http';
import {NgModule} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatNativeDateModule} from '@angular/material/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {DemoMaterialModule} from './app/app.module';

import {EventComponent} from './app/component/app.component';

@NgModule({
  imports: [
    BrowserModule,
    NoopAnimationsModule,
    FormsModule,
    HttpClientModule,
    DemoMaterialModule,
    MatNativeDateModule,
    ReactiveFormsModule,
  ],
  entryComponents: [EventComponent],
  declarations: [EventComponent],
  bootstrap: [EventComponent],
  providers: []
})
export class AppModule {}

platformBrowserDynamic().bootstrapModule(AppModule);
  • polyfills.ts - the polyfill scripts that make the application compatible with different browsers
import 'zone.js/dist/zone';
import 'hammerjs';
(window as any).global = window;
  • styles.css - the global style file
@use '@angular/material/prebuilt-themes/deeppurple-amber.css';

body {
  font-family: Roboto, Arial, sans-serif;
  margin: 0;
}
.basic-container {
  padding: 30px;
}
.version-info {
  font-size: 8pt;
  float: right;
}
  • index.html - the main HTML page that is served when you access the default port.
<!doctype html>
<html>
  <head>
    <title>Event Notifications</title>
  </head>
  <body>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons&display=block" rel="stylesheet">
    <div class="mat-app-background basic-container">
      <app-root></app-root>
    </div>
  </body>
</html>
  • angular.json - CLI configuration defaults for all projects in the workspace, including configuration options for build, serve, and test tools that the CLI uses
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "events-ui": {
      "projectType": "application",
      "schematics": {},
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/events-ui",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "aot": false,
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb",
                  "maximumError": "10kb"
                }
              ]
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "events-ui:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "events-ui:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "events-ui:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "tsconfig.app.json",
              "tsconfig.spec.json",
              "e2e/tsconfig.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        },
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "events-ui:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "events-ui:serve:production"
            }
          }
        }
      }
    }},
  "defaultProject": "events-ui"
}

This UI application was built with Angular 8.2.9 version. Using a greater version may lead to conflicts for the imports of dependencies and modules. To resolve the conflicts, modify the package.json file as follows and run npm install after saving the file.

  • package.json - the npm package dependencies that are available to all projects in the workspace
{
  "name": "events-ui",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^8.2.9",
    "@angular/cdk": "~8.2.2",
    "@angular/common": "^8.2.9",
    "@angular/compiler": "^8.2.9",
    "@angular/core": "^8.2.9",
    "@angular/forms": "^8.2.9",
    "@angular/material": "~8.2.2",
    "@angular/material-moment-adapter": "~8.2.2",
    "@angular/platform-browser": "^8.2.9",
    "@angular/platform-browser-dynamic": "^8.2.9",
    "@angular/router": "^8.2.9",
    "@types/jquery": "^3.3.31",
    "@types/sockjs-client": "^1.1.1",
    "@types/stompjs": "^2.3.4",
    "angular-in-memory-web-api": "~0.9.0",
    "core-js": "^2.6.9",
    "hammerjs": "^2.0.8",
    "jquery": "^3.4.1",
    "moment": "^2.24.0",
    "net": "^1.0.2",
    "rxjs": "^6.5.3",
    "rxjs-compat": "^6.5.3",
    "sockjs-client": "^1.4.0",
    "stompjs": "^2.3.3",
    "tslib": "^1.10.0",
    "zone.js": "~0.9.1"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^0.803.23",
    "@angular/cli": "^8.3.15",
    "@angular/compiler-cli": "^8.2.9",
    "@angular/language-service": "^8.2.9",
    "@types/jasmine": "~3.3.8",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "~8.9.4",
    "codelyzer": "^5.0.0",
    "jasmine-core": "~3.4.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.1.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.0",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.15.0",
    "typescript": "^3.5.3"
  }
}

This is how your frontend project should look like at the end:

Events App Frontend Project