Spring Boot 2+ Angular 11 + Websocket 2.3 + SockJS 1.0.2

Spring Boot Changes:

Keep only websocket spring boot starter dependency:

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-websocket</artifactId>
	</dependency>

If you are facing any dependency related issues, then try adding all the other below jars and check,

	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>sockjs-client</artifactId>
            <version>1.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>stomp-websocket</artifactId>
            <version>2.3.3</version>
        </dependency>

WebSocketConfig.java: Configuration file for websocket & route

Allowed origins must be your local host or your actual domain and make sure to declare the topic which websocket is going to expose the data to clients.

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.*;

@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")
                //.setAllowedOrigins("*")
                //.setAllowedOriginPatterns("")
                .withSockJS();
    }
}

How to push the data to websocket topic from any spring service layer ?

@Service
public class NgService {

	@Autowired
	private SimpMessagingTemplate template;

	public void processMsg(String msg) {
				// pushing the data to websocket 
				this.template.convertAndSend("/topic/ngdev-blog-data", msg);
		}
	}
}

Angular changes:

Make sure to add or install the below dependencies in your Angular 11 project,

 "net": "^1.0.2",
 "sockjs-client": "^1.3.0",
 "stompjs": "^2.3.3",

Websocket API (websocketapi.ts)

import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';
import { WebSocketShareService } from './websocketshare.service';
import { Injectable } from '@angular/core';

@Injectable()
export class WebSocketAPI {
    webSocketEndPoint: string = 'http://localhost:8081/ngdev/api/ws';
    topic: string = "/topic/ngdev-blog-data";
    stompClient: any;
    
    constructor(private websocketShare: WebSocketShareService){
         
    }
    _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);
    }	 

    onMessageReceived(message) {    
        this.websocketShare.onNewValueReceive(message.body);
    }
}

WebSocketShareService.ts:

Service class with subject to observe the pushed(new) data changes to all the consumers.

import { Injectable, OnDestroy } from "@angular/core";
import { Observable, BehaviorSubject, ReplaySubject } from "rxjs";


@Injectable()
export class WebSocketShareService implements OnDestroy {

    private blogDataSubject = new BehaviorSubject<string>(undefined);

    constructor() { }
    ngOnDestroy(): void {
        this.blogDataSubject .unsubscribe();
    }
    
    onNewValueReceive(msg: string) {        
        this.blogDataSubject .next(msg);
    }

    getNewValue(): Observable<string> {
        return this.blogDataSubject .asObservable();
    }
}

And from your Angular component, this is how you will consume and display in your template file:

DashboardComponent.ts:

import { Component, OnInit } from '@angular/core';
import { Subscription } from "rxjs";
import { WebSocketAPI } from 'src/app/shared/services/websocketapi';
import { WebSocketShareService } from 'src/app/shared/services/websocketshare.service';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
  wsData: string = 'Hello'; // default data, will be updated when the new data is pushed from spring boot backend

  constructor(private websocketService: WebSocketShareService, private webSocketAPI: WebSocketAPI) {
  }

  ngOnInit() {  
    this.webSocketAPI._connect();          
    this.onNewValueReceive();
  }

  ngOnDestroy() {
   
  }

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

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

  // method to receive the updated data.
  onNewValueReceive() {
    this.websocketService.getNewValue().subscribe(resp => {
      this.wsData = resp;
    });
  }
}

Template file just prints the variable {{wsData}}

12 comments

Leave a Reply