Angular12与Node.js中基于socket id区分收发消息并解决重复展示问题
问题修复方案
1. 消息重复展示问题修复
出现该问题有两个核心原因:
- 模板中循环消息时,同时渲染了两个消息容器,没有加判断条件,导致每条消息都会在两个容器中各显示一次
- 聊天服务中每次调用
getNewMessage方法都会新增一次socket消息监听,多次调用就会重复接收同一条消息
对应修改步骤:
第一步:修改Node.js后端代码
调整消息返回结构,携带发送者的socket id,方便前端区分消息发送方:
const app = require('express')(); const httpServer = require('http').createServer(app); const io = require('socket.io')(httpServer, { cors: {origin : '*'} }); const port = process.env.PORT || 3000; io.on('connection', (socket) => { console.log('a user connected'); socket.on('message', (message) => { // 返回结构化消息,包含发送者id和消息内容 io.emit('message', { senderId: socket.id, content: message }); }); socket.on('disconnect', () => { console.log('a user disconnected!'); }); }); httpServer.listen(port, () => console.log(`listening on port ${port}`));
第二步:修改chat.service.ts代码
将socket消息监听移到构造函数仅注册一次,同时存储当前用户的socket id用于后续判断:
import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; import { io } from "socket.io-client"; @Injectable({ providedIn: 'root' }) export class ChatService { // 调整消息类型为结构化对象 public message$: BehaviorSubject<{senderId: string, content: string}> = new BehaviorSubject(null); // 存储当前用户的socket id public mySocketId: string = ''; socket = io('http://localhost:3000'); constructor() { // 连接成功后获取当前用户的socket id this.socket.on('connect', () => { this.mySocketId = this.socket.id; }); // 全局仅注册一次消息监听 this.socket.on('message', (message) =>{ this.message$.next(message); }); } public sendMessage(message:any) { this.socket.emit('message', message); } public getNewMessage = () => { return this.message$.asObservable(); }; }
第三步:修改app.component.ts代码
调整消息列表类型,过滤空消息避免多余渲染:
import { Component } from '@angular/core'; import { ChatService } from './chat.service'; import * as moment from 'moment'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'AngularApp'; // 将chatService改为public,方便模板中访问 constructor(public chatService: ChatService){} newMessage!: string; // 调整消息列表为结构化对象数组 messageList: {senderId: string, content: string}[] = []; ngOnInit(){ this.chatService.getNewMessage().subscribe((message) => { // 过滤掉BehaviorSubject默认的空值 if(message) { let currentTime = moment().format('hh:mm:ss a'); this.messageList.push(message); } }) } sendMessage() { this.chatService.sendMessage(this.newMessage); this.newMessage = ''; } }
第四步:修改app.component.html代码
增加发送方判断逻辑,同一条消息仅渲染对应侧的容器:
<div class="container"> <div class="chat-box"> <!-- client --> <div class="client"> <h2>Simple Chat App</h2> </div> <!-- Main Chat Section --> <div class="chats" *ngFor="let message of messageList"> <!-- 对方消息显示在左侧 --> <div *ngIf="message.senderId !== chatService.mySocketId" class="client-chat"> {{message.senderId.substr(0,2)}} said--{{message.content}} </div> <!-- 自己的消息显示在右侧 --> <div *ngIf="message.senderId === chatService.mySocketId" class="my-chat"> {{message.content}} </div> </div> <!-- Input Field --> <div class="chat-input"> <input type="text" [(ngModel)]="newMessage" (keyup)="$event.keyCode == 13 && sendMessage()" placeholder="Enter Message"> <button (click)="sendMessage()" id="send-btn" class="btn btn-info"> Send </button> </div> </div> </div>
2. 收发消息分两侧展示实现
上述修改已经同步实现了该需求:
- 对方发送的消息会匹配
client-chat样式,显示在界面左侧,带发送者id前缀 - 自己发送的消息会匹配
my-chat样式,显示在界面右侧 - 原有样式不需要做任何调整即可生效
内容的提问来源于stack exchange,提问作者Amir Shahzad
相关产品推荐
相关产品推荐

