Spring Boot + Angular 8 + WebSocket Example Tutorial


In this tutorial, we will discuss how to set up a Spring Boot project with WebSocket messaging and Angular 8.

What we will build?

Basically, we will create two projects:
  1. springboot-websocket (backend): This project is used to develop WebSocket endpoint at server side using spring boot, STOMP and sock js support.
  2. angular8-springboot-websocket(frontend): This project is used to develop single page application using Angular 8 as front-end technology. This Angular 8 client application subscribes and exchanges the messages over a WebSocket.
Let's first create a backend coding part using spring boot 2+.

The source code of this tutorial available on my GitHub repository(The link has given at end of this tutorial).

Video Tutorial

The source code and demo of this tutorial explained in below YouTube video. Subscribe to our youtube channel at Java Guides - YouTube Channel so that you will receive all the productive videos updates.

Backend - Spring Boot App Development

1. Creating a Spring Boot Application

There are many ways to create a Spring Boot application. You can refer below articles to create a Spring Boot application.

2. Project Structure

Refer below screenshot to create project or packaging structure:

3. Create a resource representation class

We are exchanges message in JSON format over STOMP protocol so let's create two Java classes Greeting and HelloMessage.

HelloMessage

package net.javaguides.springboot.websocket.model

public class HelloMessage {

    private String name;

    public HelloMessage() {}

    public HelloMessage(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Greeting.java

package net.javaguides.springboot.websocket.model

public class Greeting {

    private String content;

    public Greeting() {}

    public Greeting(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}
Spring will use the Jackson JSON library to automatically marshal instances of type Greeting into JSON.

Next, you’ll create a controller to receive the hello message and send a greeting message.

4. Create a message-handling controller

In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to @Controller classes. For example, the GreetingController is mapped to handle messages to destination "/hello".
package net.javaguides.springboot.websocket.controller;

import org.springframework.messaging.handler.annotation.MessageMapping;

import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;

import net.javaguides.springboot.websocket.model.Greeting;
import net.javaguides.springboot.websocket.model.HelloMessage;

@Controller
public class GreetingController {


    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(1000); // simulated delay
        return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
    }

}
The @MessageMapping annotation ensures that if a message is sent to destination "/hello", then the greeting() method is called.

The payload of the message is bound to a HelloMessage object which is passed into greeting().

5. Configure Spring for STOMP messaging

Now that the essential components of the service are created, you can configure Spring to enable WebSocket and STOMP messaging.
Create a Java class named WebSocketConfig that looks like this:
package net.javaguides.springboot.websocket.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").setAllowedOrigins("http://localhost:4200").withSockJS();
    }
}
WebSocketConfig is annotated with @Configuration to indicate that it is a Spring configuration class. The @EnableWebSocketMessageBroker enables WebSocket message handling, backed by a message broker.

We have handled the CORS issue with the following code:
public void registerStompEndpoints(StompEndpointRegistry registry) {
     registry.addEndpoint("/ws").setAllowedOrigins("http://localhost:4200").withSockJS();
}
And that’s it for Back-End! Now we need to create Front-End part of the application to start the socket communication.

Front-End - Angular 8 Client App Development

I assume that you have installed Node.js. Now, we need to check the Node.js and NPM versions. Open the terminal or Node command line then type this commands.
C:\Angular>node -v
v10.15.3

C:\Angular>npm -v
6.9.0

1. Install the latest version of Angular CLI

To install or update Angular 7 CLI, type this command in the Terminal or Node Command Line.
npm install -g @angular/cli
Now, let's check the latest version of Angular CLI:
C:\angular>ng --version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 8.0.1
Node: 10.15.3
OS: win32 x64
Angular:
...

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.800.1
@angular-devkit/core         8.0.1
@angular-devkit/schematics   8.0.1
@schematics/angular          8.0.1
@schematics/update           0.800.1
rxjs                         6.4.0

2. Create Angular 8 client application using Angular CLI

The Angular CLI is a command-line interface tool that you use to initialize, develop, scaffold, and maintain Angular applications.
If you are new to Angular CLI then check out official documentation at https://cli.angular.io.
Let's use below command to generate an Angular 8 Client application. We name this project as "angular8-springboot-websocket".
ng new angular8-springboot-websocket
Inside the project we have to install tree libraries with commands:
  • npm install stompjs;
  • npm install sockjs-client
  • npm install jquery (just to quickly access the DOM elements)

3. package.json

This file configures npm package dependencies that are available to all projects in the workspace. Note that angular version 8.0.0 in dependencies section in below file.
{
  "name": "angular8-springboot-websocket",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "~8.0.0",
    "@angular/common": "~8.0.0",
    "@angular/compiler": "~8.0.0",
    "@angular/core": "~8.0.0",
    "@angular/forms": "~8.0.0",
    "@angular/platform-browser": "~8.0.0",
    "@angular/platform-browser-dynamic": "~8.0.0",
    "@angular/router": "~8.0.0",
    "jquery": "^3.4.1",
    "net": "^1.0.2",
    "rxjs": "~6.4.0",
    "sockjs-client": "^1.3.0",
    "stompjs": "^2.3.3",
    "tslib": "^1.9.0",
    "zone.js": "~0.9.1"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.800.0",
    "@angular/cli": "~8.0.1",
    "@angular/compiler-cli": "~8.0.0",
    "@angular/language-service": "~8.0.0",
    "@types/node": "~8.9.4",
    "@types/jasmine": "~3.3.8",
    "@types/jasminewd2": "~2.0.3",
    "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.4.3"
  }
}

4. WebSocket API

Let's create a WebSocketAPI typescript file and add the following code to it:
import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';
import { AppComponent } from './app.component';

export class WebSocketAPI {
    webSocketEndPoint: string = 'http://localhost:8080/ws';
    topic: string = "/topic/greetings";
    stompClient: any;
    appComponent: AppComponent;
    constructor(appComponent: AppComponent){
        this.appComponent = appComponent;
    }
    _connect() {
        console.log("Initialize WebSocket Connection");
        let ws = new SockJS(this.webSocketEndPoint);
        this.stompClient = Stomp.over(ws);
        const _this = this;
        _this.stompClient.connect({}, function (frame) {
            _this.stompClient.subscribe(_this.topic, function (sdkEvent) {
                _this.onMessageReceived(sdkEvent);
            });
            //_this.stompClient.reconnect_delay = 2000;
        }, this.errorCallBack);
    };

    _disconnect() {
        if (this.stompClient !== null) {
            this.stompClient.disconnect();
        }
        console.log("Disconnected");
    }

    // on error, schedule a reconnection attempt
    errorCallBack(error) {
        console.log("errorCallBack -> " + error)
        setTimeout(() => {
            this._connect();
        }, 5000);
    }

 /**
  * Send message to sever via web socket
  * @param {*} message 
  */
    _send(message) {
        console.log("calling logout api via web socket");
        this.stompClient.send("/app/hello", {}, JSON.stringify(message));
    }

    onMessageReceived(message) {
        console.log("Message Recieved from Server :: " + message);
        this.appComponent.handleMessage(JSON.stringify(message.body));
    }
}

5. app.component.ts -> AppComponent

Defines the logic for the app's root component, named AppComponent. The view associated with this root component becomes the root of the view hierarchy as you add components and services to your app.
import { Component } from '@angular/core';
import { WebSocketAPI } from './WebSocketAPI';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angular8-springboot-websocket';

  webSocketAPI: WebSocketAPI;
  greeting: any;
  name: string;
  ngOnInit() {
    this.webSocketAPI = new WebSocketAPI(new AppComponent());
  }

  connect(){
    this.webSocketAPI._connect();
  }

  disconnect(){
    this.webSocketAPI._disconnect();
  }

  sendMessage(){
    this.webSocketAPI._send(this.name);
  }

  handleMessage(message){
    this.greeting = message;
  }
}

6. app.component.html

Defines the HTML template associated with the root AppComponent.
<!--The content below is only a placeholder and can be replaced.-->
<<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
  enabled. Please enable
  Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">

    <div class="row">
        <div class="col-md-12">
           {{ greeting }}
        </div>
    </div>
    
  <div class="row">
      <div class="col-md-6">
          <form class="form-inline">
              <div class="form-group">
                  <label for="connect">WebSocket connection:</label>
                  <button (click)="connect()" class="btn btn-default" type="submit">Connect</button>
                  <button (click)="disconnect()" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                  </button>
              </div>
          </form>
      </div>
      <div class="col-md-6">
          <form class="form-inline">
              <div class="form-group">
                  <label for="name">What is your name?</label>
                  <input type="text" [(ngModel)]="name" class="form-control" name="name"> <br>
              </div>
              <button (click)="sendMessage()" class="btn btn-default" type="submit">Send</button>
          </form>
      </div>

      <p> {{ greeting }}</p>
  </div>
 
</div>
<router-outlet></router-outlet>

7. index.html

Here is code for index.html. Add the following code to index.html file:
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Angular8SpringbootWebsocket</title>
  <base href="/">
  <!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" 
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">

  <script>
    if (global === undefined) {
      var global = window;
    }
  </script>
</head>
<body>
  <app-root></app-root>
</body>
</html>

8. app.module.ts

Defines the root module, named AppModule, that tells Angular how to assemble the application. Initially declares only the AppComponent. As you add more components to the app, they must be declared here.
We are using the HTML form to submit name so add the following code to AppModule:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms'

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
Note that we have added FormsModule to work with HTML forms.

9. Running Angular 8 Client Application

Let's run the above developed Angular App with a command:
ng serve
By default, the Angular app runs on 4200 port but you can change default port with the following command:
ng serve --port 4201

Demo

Hit this http://localhost:4201/ link in the browser will open below the web page. Click on connect button, which will connect o WebSocket (observe in console logs for WebSocket connection)
Now, once you connect with WebSocket, let's enter"Ramesh Fadatare"  text in what's your name input field and hit send button. The server will send a message to the subscribed client via a callback function. Below screenshot demonstrates how the Angular Client app receives a message from the server.

Source Code on GitHub

The source code of this tutorial available on my GitHub repository at https://github.com/RameshMF/angular8-springboot-websocket.

Follow below steps to run front-end Angular application:

>> cd front-end/angular8-springboot-websocket
>> npm install
>> ng serve

Follow below steps to run back-end application:
1. Extract the back-end zip file
2. cd back-end/springboot-stomp-websocket
3. mvn spring-boot:run
The source code examples available on my GitHub Repository.

Comments

  1. This comment has been removed by the author.

    ReplyDelete
  2. Thank you for this awesome tutorial

    ReplyDelete
  3. How i will initialize the web socket API if my app component has a lot of services, for example...

    constructor(
    @Inject(DOCUMENT) private document: any,
    private _platform: Platform,
    private idle: Idle,
    private keepalive: Keepalive,
    private router: Router,
    private gw: ApiGateway,
    private snackBar: SnackBarService,
    private userService: UserService

    ) {

    ReplyDelete
    Replies
    1. What do u think if i change the handlerMessage with a event emmiter, can get the value directly in app component. Let me know what is ur opinion about?

      Delete

Post a Comment