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.
All Angular 8 tutorials and examples at https://www.javaguides.net/p/angular-8-tutorial-with-examples.html

What we will build?

Basically, we will create two projects:
  1. springboot-websocket (backend): This project is used to develop a WebSocket endpoint at the 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 is available on my GitHub repository(The link has given at end of this tutorial).

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 to the below screenshot to create a 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 the 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 the 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 the official documentation at https://cli.angular.io.
Let's use the 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 the dependencies section in the 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 WebSocket API 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 the 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 the 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 a 4200 port but you can change the 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 the 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 the send button. The server will send a message to the subscribed client via a callback function. The 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 the below steps to run the 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. Thank you for this awesome tutorial

    ReplyDelete
  2. 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
  3. Thanks a lot for the tutorial

    ReplyDelete
  4. I need to send message after updating a record in db to client.
    How to achieve this in spring boot.
    Spring application will push notifications to client after every change in record

    ReplyDelete
  5. that greeting field is not updating after after the success ,can you pls tell me why it is

    ReplyDelete
  6. I have this error when trying to connect:

    Access to XMLHttpRequest at 'http://localhost:8080/ws/info?t=1576698440026' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

    ReplyDelete
    Replies
    1. download and Enable the cors extension from chrome store. this will resolve the issue.

      Delete
  7. Hello,

    it's great with the tutorial, thank you so much for good job :-)

    I've tried it and works very well by using ng serve -o.

    But there is indeed a little bit problem by running it after running

    ng build --prod

    Within the Directory dist/angular8-springboot-websocket there is an index.html.

    The application does not work by opening of it:

    Error:

    Access to script at 'file:///C:/runtime-es2015.edb2....js' from orgin 'null' has bin blocked by CORS Policy: cross index.html:1 origin requests are only supported for protocol schemes: http,data, Chrome, Chrome-Extension, https.

    The same error for other js-files polyfills-es2015..., main-es2015.... and so on


    Question: What is wrong and how can this Problem be solved ?

    Thanks in advanced

    and with best reguards

    Mr. Dang

    ReplyDelete
  8. I have a question. How to print the conversation on screen except console.???..

    ReplyDelete
  9. Backend folder is empty could you please upload that also

    ReplyDelete
  10. Hi, this only works for me if I 1)
    implement OnInit in step 5:
    export class AppComponent implements OnInit { etc etc
    and 2) if I do not switch to another port.
    (why would you want to change ports in Angular for this? You set it up in the back end to only allow port 4200. If you switch to 4201 you get CORS errors)

    ReplyDelete
    Replies
    1. I'm using Angular 9 by the way. Although I think both issues would also be present in older versions.

      Delete
  11. ERROR in ./node_modules/stompjs/lib/stomp-node.js
    Module not found: Error: Can't resolve 'net' in 'D:\angular8-springboot-websocket\node_modules\stompjs\lib'

    ReplyDelete
    Replies
    1. you have to install npm "net" package:
      npm install --save-dev net

      Delete
  12. After hours of trying to print the message to the screen I finally managed

    Just had to write the handleMessage + connect inside the component i.e -> the connec . subscription part

    ReplyDelete
    Replies
    1. can you dive a bit further into your solution? It would be great if you can share the code !!

      Delete
    2. Just remember what a service and component are meant for

      Service gives API access to url and components implements the "methods" and renders the data to the view (e.g .html)

      So in the api/service connect func... you can return a stompClient after Stomp.over(ws), and in the component where you'll call the api_connect handle the message output

      That's it, two way data binding will handle the rest

      Delete
    3. app.component.ts

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

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

      Delete
  13. The port 8080 is common for all or it might be different.
    http://localhost:8080/ws

    ReplyDelete
  14. Thanks for the article.
    What if I want to share WebSocketAPI with mutiple components?

    ReplyDelete
    Replies
    1. _connect service / API must return stompClient after connection to Stomp.over(ws)

      And implement the rest in their components

      Delete
  15. Hi. How about add Auth2 and use wss?

    ReplyDelete
  16. in reconnect function, this is not accessible, it is appearing as undefined.

    ReplyDelete
  17. I need to display the greeting in webpage. No idea how it is

    ReplyDelete
  18. Great tutorial!

    I know it's just to show something quickly, but please don't advice people to install jquery in an Angular project, that's really not a good thing to do!

    ReplyDelete
  19. Great tutoriel but i need ti send notification to a specific user(profil) ? any idea

    ReplyDelete
  20. 'this' keyword is undefined on errorCallBack function. Any solution for this

    ReplyDelete
    Replies
    1. adding this.errorCallBack.bind(this) worked for me

      Delete

Post a Comment

Leave Comment