I am encountering difficulties loading the message view while implementing a chat application with Angular17, simulating WhatsApp, and using spring-boot-starter-websocket in the backend. I've provided the relevant code snippets and repositories below for reference:
Spring Boot WebSocket Repository
github.com/franki-wolf1/Springboot-websocketrepo angular en
github.com/franki-wolf1/chat-angularwhatsapp demo en app-social-post.web.app/chat/
Issue:
The problem arises when opening two chat views, each simulating a different user. Although the messages are successfully reaching the backend, the view does not update to display the messages sent from one user to another.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.chat</groupId>
<artifactId>springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>3.2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
then I leave the controller in the backend
package com.chat.springboot.controller;
import com.chat.springboot.dto.ChatMessage;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import java.text.SimpleDateFormat;
import java.util.Date;
@Controller
public class WebSocketController {
@MessageMapping("/chat/{roomId}")
@SendTo("/topic/{roomId}")
public ChatMessage chat(@DestinationVariable String roomId, ChatMessage message) {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
return new ChatMessage(message.getMessage(), message.getUser(), time);
}
}
Next I leave the backend socket configuration
package com.chat.springboot.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
//AbstractMessageBrokerConfiguration
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
System.out.println(registry);
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat-socket");
registry.addEndpoint("/chat-socket")
.setAllowedOrigins("http://localhost:4200")
.withSockJS();
}
}
Whit the porpertis:
server.port=3000
on the frontend, I have these services
import { Injectable } from '@angular/core';
import { Stomp } from '@stomp/stompjs';
import SockJS from 'sockjs-client';
import { modelChatMessage } from '../models/modelChatMessage';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ChatServices {
private stompCliente: any;
private messageSubject: BehaviorSubject<modelChatMessage[]> =
new BehaviorSubject<modelChatMessage[]>([]);
constructor() {
this.initConnectionSocket();
}
initConnectionSocket() {
const url = '//localhost:3000/chat-socket';
const socket = new SockJS(url);
this.stompCliente = Stomp.over(socket);
}
joinRoom(roomID: any) {
this.stompCliente.connect({}, () => {
this.stompCliente.subscribe(`/topic/${roomID}`, (messages: any) => {
//this.showMessageOutput(JSON.parse(messages.body))
const messageConten = JSON.parse(messages.body);
const currentMessages = this.messageSubject.getValue();
currentMessages.push(messageConten);
this.messageSubject.next([...currentMessages]); // Usar spread operator para mantener la inmutabilidad del array
});
});
}
showMessageOutput(messageOutput: any) {
var response = document.getElementById('response');
const p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(messageOutput.from + ': '+
response?.appendChild(p)))
}
sendMessage(roomID: string, chatMessage: modelChatMessage) {
this.stompCliente.send(`/app/chat/${roomID}`, {}, JSON.stringify(chatMessage));
}
getMessageSubject() {
return this.messageSubject.asObservable();
}
}
my component is
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChatServices } from '../../services/chat.service';
import { ActivatedRoute } from '@angular/router';
import { modelChatMessage } from '../../models/modelChatMessage';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-chat',
standalone: true,
imports: [
CommonModule, FormsModule
],
templateUrl: './chat.component.html',
styleUrl: './chat.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class ChatComponent {
messageInput: string = '';
userId: string="";
messageList: any[] = [{
message: 'hola',
user: '',
state: '',
}];
constructor(private chatServices: ChatServices,
private route: ActivatedRoute
){
}
ngOnInit(): void {
this.userId = this.route.snapshot.params["userId"];
this.chatServices.joinRoom(this.userId);
this.listenerMessage();
}
sendMessage() {
console.log('22222222::::::::::_'+this.userId);
const chatMessage = {
message: this.messageInput,
user: this.userId,
state: this.userId + ' send'
}as modelChatMessage
this.chatServices.sendMessage(this.userId, chatMessage);
this.messageInput = '';
}
listenerMessage() {
this.chatServices.getMessageSubject().subscribe((messages: any) => {
this.messageList = messages.map((item: any)=> ({
...item,
message_side: item.user === this.userId ? 'sender': 'receiver'
}))
});
}
}
Ans the view is:
<div class="chat_window">
<div class="title d-flex align-items-center">
<img src="https://png.pngtree.com/png-vector/20221018/ourmid/pngtree-whatsapp-phone-icon-png-image_6315989.png" width="45px" height="45px">
<h3 class="wsp" (click)="listenerMessage()">WhatsApp</h3>
</div>
<div class="top_menu">
<div class="buttons">
<div class="button close"></div>
<div class="button minimize"></div>
<div class="button maximize"></div>
</div>
</div>
<ul class="messages">
<li class="message right" *ngFor="let item of messageList"
[ngClass]="{'left': item.message_side === 'receiver', 'right': item.message_side === 'sender'}">
<div class="avatar"></div>
<div class="text_wrapper">
<div class="text">{{ item.message }}</div>
</div>
</li>
</ul>
<div class="bottom_wrapper clearfix">
<div class="message_input_wrapper">
<!-- Corregir el binding ngModel y agregar el atributo name -->
<input class="message_input" placeholder="Message..." [(ngModel)]="messageInput" name="messageInput" />
</div>
<div class="send_message" (click)="sendMessage()">
<div class="icon"></div>
<div class="text">Send</div>
</div>
</div>
</div>
Additional Information:
- Backend server is configured with
server.port=3000.