Home > Mobile >  Socket.io event listener called multiple times (angular 9)
Socket.io event listener called multiple times (angular 9)

Time:01-25

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!!

CodePudding user response:

For what it's worth, don't leave subscriptions hanging around. Each time component is loaded a subscription is added, but never removed. This may, but may not be the cause. Anyway, I refactored and implemented a way to unsubscribe(), give it a shot.

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
  export class PlatinumChatPage implements OnInit, OnDestroy {
    message: string;
    authorId: any;
    channel: any;
    threadChat: any[] = [];
    channelError: string = '';
    subs = [];

    constructor(
      private chatService: ChatService,
    ) {
      this.channel = localStorage.getItem('channelId');
    }

    ngOnDestroy() {
      this.subs.forEach(sub => sub.unsubscribe());
    }

    ngOnInit() {
      //load channel history
      this.loadChannelHistory();
  
      // get new message listener
      const chatSub = this.chatService.getMessage().subscribe((msg: any) => {
          this.threadChat.push(msg);
      });
  
      // error listener
      const errorSub = this.chatService.getChannelError().subscribe(errorMessage => {
        this.channelError = errorMessage;
      });
  
      //channel history listener
      const historySub = this.chatService.getChannelHistory().subscribe((threads: any) => {
        this.threadChat = threads;
      });
      this.subs.push(chatSub, errorSub, historySub);
    }
  
    loadChannelHistory(){
      const channelId = JSON.parse(this.channel);
      this.chatService.loadChannelHistory(channelId && channelId._id || null);
    }
  
    onSendMessage() {
        this.chatService.emitMessage(this.message, this.authorId, this.channel);
    }
  }
  •  Tags:  
  • Related