1

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

2
  • Is it possible for you to create a minimal working example on stackblitz? Commented Jan 24, 2022 at 19:29
  • Also include relevant part of the html where messages show up. Commented Jan 24, 2022 at 19:31

1 Answer 1

1

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);
    }
  }
Sign up to request clarification or add additional context in comments.

1 Comment

Joosep.P - Your solution worked for me. Thanks for giving me right solution. Thanks a lot. Now issue is gone away, listener call only once.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.