Home > Back-end >  Update the mailbox of all users in real time after sending a message
Update the mailbox of all users in real time after sending a message

Time:01-11

I have a simple chat with groups, when a user sends a message or enters the group the notification reaches all the users that belong to that group in real time.

When they join groups

groups

The problem is that the div that displays the messages is only updated in the current user's window. And it is not updated in the rest of the open windows, but if, for example, when I focus the text box, if the information of the div is updated in the user window that focuses the text box.

When a message is sent

groups2

Now i have to focus on the text box to show me the messages

fous

ANGULAR CODE

app.component.html

<div *ngIf="!joined">
  <strong>Create a group</strong><br><br>
  <div>
    <label>Group name:</label>
    <input type="text" name="groupName" [(ngModel)]="groupName" />
  </div><br>
  <div>
    <label>User name:</label>
    <input type="text" name="userName" [(ngModel)]="userName" />
  </div><br>
  <button type="button" (click)="join()">Enter</button>
</div>

<div *ngIf="joined">
  <div id="chat">
    <div *ngFor="let message of conversation">
      <span>{{message.userName}} : {{message.message}}</span>
    </div>
  </div>
  <input type="text" [(ngModel)]="messageToSend" name="messageToSend" />
  <button (click)="sendMessage()">Send</button>
  <button (click)="leave()">Leave</button>
</div>

app.component.ts

import { Component, OnInit } from '@angular/core';
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit{

  public userName = '';
  public groupName = 'demo';
  public messageToSend = '';
  public joined = false;
  public conversation: NewMessage[] = [{
    message: 'Welcome',
    userName: 'Chat'
  }];

  private connection: HubConnection;

  constructor() {
    this.connection = new HubConnectionBuilder()
      .withUrl('https://localhost:7133/chat')
      .build();

    this.connection.on("NewUser", message => this.newUser(message));
    this.connection.on("NewMessage", message => this.newMessage(message));
    this.connection.on("LeftUser", message => this.leftUser(message));
  }

  ngOnInit(): void {
    this.connection.start()
      .then(_ => {
        console.log('Connection Started');
      }).catch(error => {
        return console.error(error);
      });
  }

  public join() {
    this.connection.invoke('JoinGroup', this.groupName, this.userName)
      .then(_ => {
        this.joined = true;
      });
  }

  public sendMessage() {
    const newMessage: NewMessage = {
      message: this.messageToSend,
      userName: this.userName,
      groupName: this.groupName
    };

    this.connection.invoke('SendMessage', newMessage)
      .then(_=> this.messageToSend = '');
  }

  public leave() {
    this.connection.invoke('LeaveGroup', this.groupName, this.userName)
      .then(_ => this.joined = false);
  }

  private newUser(message: string) {
    console.log(message);
    this.conversation.push({
      userName: 'Chat',
      message: message
    });
  }

  private newMessage(message: NewMessage) {
    console.log(message);
    this.conversation.push(message);
  }
  private leftUser(message: string) {
    console.log(message);
    this.conversation.push({
      userName: 'Sistema',
      message: message
    });
  }
}
export class NewMessage {
  userName: string;
  message: string;
  groupName?: string;
}

API C# CODE

Hub

namespace ApiChat.Hubs
{
    public class ChatHub:Hub
    {
        public async Task JoinGroup(string groupName, string userName)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
            await Clients.Group(groupName).SendAsync("NewUser", $"{userName} joined the group");
        }
        public async Task LeaveGroup(string groupName, string userName)
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
            await Clients.Group(groupName).SendAsync("LeftUser", $"{userName} left the group");
        }
        public async Task SendMessage(NewMessage message)
        {
            await Clients.Group(message.GroupName).SendAsync("NewMessage", message);
        }
    }
    public record NewMessage(string UserName, string Message, string GroupName);
}

Program.cs

builder.Services.AddSignalR();
app.UseCors(builder =>
{
    builder
        .AllowAnyHeader()
        .AllowAnyMethod()
        .AllowCredentials()
        .WithOrigins("http://localhost:4200");
});
app.MapHub<ChatHub>("/chat");

CodePudding user response:

This is likely a change detection dealy.

Right now, it's just pushing to the messages array; Angular won't detect this in order to do the rendering update.

The cheap way of doing it, once you've pushed your message into the array is a simple this.messages = [...this.messages]; in order to create a NEW array.

This'll kick Angular into recognising a change - it recognises whole object changes, not internal changes.

E.g. Replace the whole array (per above), replace the whole object (this.obj = {...this.obj};), replace the value-type (this.val = 3;).

It won't detect the internal values, no array changes (this.arr.push(1);), no object property changes (this.obj.val = 3;). The caveat being that if you have a direct link to that property (this.obj.val) from your template then it will detect changes to that one property.

It'll do as a quick and dirty method, but obviously you don't really want to be copying an entire array every time a message comes through once you start to build up a fair stack of messages. There are better methods than that for triggering Angular's change detection that don't include entire array copies.

See Angular's own guide for change detection.

CodePudding user response:

Using NgZone in this way fixes the problem.

this.ngZone.run(()=>this.conversation.push(message));
  • Related