I've encontered a problem while developping an application using ASP.NET Core, Angular 5 and SignalR.
As a test, I made a simple chat app based on this sample : https://codingblast.com/asp-net-core-signalr-chat-angular/
But, when I modify the chat.component.ts file from
this.hubConnection.on('sendToAll', (nickname: string, message: string) => { this.messages.push(new Message(nickname, message)); });
// or this line
this.hubConnection.on('sendToAll', (nickname: string, message: string) => { this.addMessage(nickname, message); });
to
this.hubConnection.on('sendToAll', this.addMessage);
the DOM is not updated.
I've gone through step-by-step and I managed to figure out that when the callback arrives, it's actually another instance of the component... So every fields are undefined.
Here is the full component :
chat.component.html
<section class="card">
<div class="card-body" *ngIf="messages !== undefined">
<div *ngFor="let msg of messages">
<strong>{{msg.nickname}}</strong>: {{msg.message}}
</div>
</div>
<div class="card-footer">
<form class="form-inline" (ngSubmit)="sendMessage()" #chatForm="ngForm">
<div class="form-group">
<label class="sr-only" for="nickname">Nickname</label>
<input type="text" id="nickname" name="nickname" placeholder="Nickname" [(ngModel)]="nickname" required />
</div>
<div class="form-group">
<label class="sr-only" for="message">Message</label>
<input type="text" id="message" name="message" placeholder="Your message" [(ngModel)]="message" required />
</div>
<button class="btn btn-sm" type="submit" id="sendmessage" [disabled]="!chatForm.valid">Send</button>
</form>
</div>
</section>
chat.component.ts
import { Component, OnInit } from '@angular/core';
import { HubConnection } from '@aspnet/signalr';
import { Message } from '../../models/message';
@Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.css']
})
export class ChatComponent implements OnInit {
private hubConnection: HubConnection;
public nickname: string;
public message: string;
public messages: Message[];
constructor() {
this.messages = [];
}
ngOnInit() {
this.hubConnection = new HubConnection('http://localhost:55233/chat');
this.hubConnection.start().then(() => console.log('connection started')).catch(err => console.error(err));
this.hubConnection.on('sendToAll', this.addMessage);
}
private addMessage(nickname: string, message: string): void {
// this line is useful to avoid undefined error
// due to the callback pointing to another instance of this component...
if (this.messages === undefined) {
this.messages = [];
}
this.messages.push(new Message(nickname, message));
}
public sendMessage(): void {
this.hubConnection
.invoke('sendToAll', this.nickname, this.message)
.catch(err => console.error(err));
}
}
Here is what I've tried :
How can I use this line this.hubConnection.on('sendToAll', this.addMessage);
in this context?
Thanks for your help.
EDIT :
Thanks to Supamiu, here's the solution :
this.hubConnection.on('sendToAll', this.addMessage.bind(this));
Javascript provides a tool to inject context into a method call: bind.
You should change this.addMessage
to this.addMessage.bind(this)
.
Keep in mind that there's other solutions, like the one trichetriche explained, another one would be to change addMessage
for an arrow function, as arrow functions keep this
context:
private addMessage = (nickname: string, message: string) => {
// this line is useful to avoid undefined error
// due to the callback pointing to another instance of this component...
if (this.messages === undefined) {
this.messages = [];
}
this.messages.push(new Message(nickname, message));
}
By making this, you could still use this.addMessage
without having to bind this
context.