Frontend (Angular 9)
I'm implementing chat functionality with Socket.io and angular 9. Created a service and called for socket connection and event handling as below.
@Injectable({
providedIn: 'root',
})
export class ChatService {
private socket;
public channelError = new BehaviorSubject(null);
public channelHistory = new BehaviorSubject(null);
public channelMessage = new BehaviorSubject(null);
public channelReaction = new BehaviorSubject(null);
constructor(
) {}
establishSocketConnection = (userId) => {
this.socket = io(`${chatUrl}/chat`, {
path: '/socket.io',
query: {
user: userId,
},
});
this.socket.on('channel-history', (threads: any) => {
this.channelError.next('');
this.channelHistory.next(threads);
});
this.socket.on('message-local', (message: any) => {
this.channelMessage.next(message);
});
}
// socket (emit) events
public emitMessage = (message, authorId, channel) => {
this.socket.emit('message', message, authorId, channel);
}
public loadChannelHistory = (channelId) => {
this.socket.emit('load-channel-history', channelId);
}
// socket (on) event listeners
public getChannelHistory(): Observable<any> {
return this.channelHistory.asObservable();
}
public getMessage(): Observable<any> {
return this.channelMessage.asObservable();
}
}
I'm calling method "establishSocketConnection" once after login to establish socket connect and register socket listeners. I have registered all socket listeners in this method.
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { LoginService } from '../../services/login.service';
import { StorageService } from '../../services/storage.service';
import { ProductTypeService } from '../../services/product-type.service';
import { UserSidebarService } from '../../services/user-sidebar.service';
import { Storage } from '@ionic/storage';
import { ChannelTypeService } from 'src/app/services/channel-type.service';
import { ChatService } from 'src/app/services/chat.service';
@Component({
selector: 'app-menu',
templateUrl: './menu.page.html',
styleUrls: ['./menu.page.scss'],
})
export class MenuPage implements OnInit {
activeSettingChild: any;
channelMaintitle: any;
channels: any;
selectedChat: any;
constructor(
private router: Router,
public loginServ: LoginService,
public storageServ: StorageService,
public productTypeServ: ProductTypeService,
public userSidebarServ: UserSidebarService,
public storage: Storage,
private channelTypeServ: ChannelTypeService,
private chatService: ChatService
) {
}
ngOnInit() {
//establish socket connection
let userId = this.storageServ.get('userId');
if (userId) {
this.chatService.establishSocketConnection(userId);
}
this.selectedChat = localStorage.getItem('selectedChat');
this.getAllChannels();
}
getAllChannels() {
this.channelTypeServ.getAllChannelTypes().subscribe(
(channel: any) => {
this.channels = channel.data.channels;
},
(error) => { }
);
}
onSelectChannel(channel) {
this.activeSettingChild = channel.slug;
this.storageServ.set('activeSettingChild', channel.slug);
this.storageServ.set('lockedPageContent', channel.lockedPageContent);
this.storageServ.set('channelTitle', channel.title);
this.storageServ.set('channelId', JSON.stringify(channel));
this.channelMaintitle = channel.settingId.title;
this.storageServ.set('channelMaintitle', this.channelMaintitle);
this.router.navigate(
[`/platinum-chat/${this.selectedChat}/${'channel/' + channel.slug}`],
{ replaceUrl: true }
);
}
}
I'm using this chat service as follow in one component. Component:
import { Component, OnInit } from '@angular/core';
import { ChatService } from 'src/app/services/chat.service';
@Component({
selector: 'app-platinum-chat',
templateUrl: './platinum-chat.page.html',
styleUrls: ['./platinum-chat.page.scss'],
})
export class PlatinumChatPage implements OnInit {
message: string;
authorId: any;
channel: any;
threadChat: any[] = [];
channelError: string = '';
constructor(
private chatService: ChatService,
) {
this.channel = localStorage.getItem('channelId');
}
ngOnInit() {
//load channel history
this.loadChannelHistory();
// get new message listener
this.chatService.getMessage().subscribe((msg: any) => {
this.threadChat.push(msg);
});
// error listener
this.chatService.getChannelError().subscribe(errorMessage => {
this.channelError = errorMessage;
});
//channel history listener
this.chatService.getChannelHistory().subscribe((threads: any) => {
this.threadChat = threads;
});
}
loadChannelHistory(){
const channelId = JSON.parse(this.channel);
this.chatService.loadChannelHistory(channelId && channelId._id || null);
}
onSendMessage() {
this.chatService.emitMessage(this.message, this.authorId, this.channel);
}
}
The issue is, it's calls event listener "on message-local" multiple times when i send new message. As i have debugged around this issue, as per some suggetions it's issue with duplicate event listener registration. In my case, i'm not calling method "establishSocketConnection" more than once, then how it's registers duplicate listener (message-local) and calls multiple times on new message.
Note: not issue from server side. I'm only send single emit from server side but it's called multiple times in frontend.
Here i have recorded my issue https://www.loom.com/share/d9142c73c6c54cb58802ac3edf704bc5
I don't understand what is the issue with current codebase.
Can you guys please help me with this issue. Thanks in advance!!