Quick start
Install the package, declare a contract, and call a hub from a component.
Requirements
- Node 20.19 or later.
@microsoft/signalr8, 9, or 10.- A server with ASP.NET Core SignalR. The older ASP.NET SignalR for .NET Framework uses a different protocol and does not work with this client.
- Angular 20, 21, or 22. The
rxjs-interopentry point also needsrxjs7.6 or later.
Install
Install the package and its SignalR peer:
npm install @dammers/use-signalr-angular @microsoft/signalrSet up
Write the server hub
The samples in this documentation use this hub:
using System.Collections.Concurrent;
using Microsoft.AspNetCore.SignalR;
namespace Server.Hubs;
public record ChatMessage(string RoomId, string User, string Text, DateTimeOffset SentAt);
/// <summary>Teaching hub: chat rooms built on SignalR groups.</summary>
public class RoomHub : Hub
{
private const int HistoryLimit = 50;
private static readonly ConcurrentDictionary<string, Queue<ChatMessage>> History = new();
// Adding a connection that is already in the group changes nothing, so the client can retry.
public Task JoinRoom(string roomId) => Groups.AddToGroupAsync(Context.ConnectionId, roomId);
public Task LeaveRoom(string roomId) => Groups.RemoveFromGroupAsync(Context.ConnectionId, roomId);
public Task SendMessage(string roomId, string text)
{
var message = new ChatMessage(roomId, Context.User?.Identity?.Name ?? "anonymous", text, DateTimeOffset.UtcNow);
var history = History.GetOrAdd(roomId, _ => new Queue<ChatMessage>());
lock (history)
{
history.Enqueue(message);
if (history.Count > HistoryLimit) history.Dequeue();
}
return Clients.Group(roomId).SendAsync("MessageReceived", message);
}
public ChatMessage[] GetHistory(string roomId)
{
if (!History.TryGetValue(roomId, out var history)) return [];
lock (history) return history.ToArray();
}
}Declare the contract
List each hub path, its events, and its methods. Names and argument types must match the server hub:
import { event, method } from "@dammers/use-signalr-angular";
export interface ChatMessage {
roomId: string;
user: string;
text: string;
sentAt: string;
}
// Keys are hub paths. Names and argument types match the server hub.
export const hubs = {
"/hubs/rooms": {
events: {
MessageReceived: event<[message: ChatMessage]>(),
},
methods: {
JoinRoom: method<[roomId: string]>(),
LeaveRoom: method<[roomId: string]>(),
SendMessage: method<[roomId: string, text: string]>(),
GetHistory: method<[roomId: string], ChatMessage[]>(),
},
},
"/hubs/presence": {
lazy: true,
graceMs: 2000,
events: {
OnlineCount: event<[count: number]>(),
},
},
};Create the client
Pass the contract to createSignalRClient. The call returns the typed helpers that your components use:
import { createSignalRClient } from "@dammers/use-signalr-angular";
import { hubs } from "./contract";
export const {
provideSignalR,
injectHubEvent,
injectHubInvoke,
injectHubSend,
injectHubTeardown,
injectHubStatus,
injectOnReconnected,
injectKeepHubAlive,
} = createSignalRClient({ hubs });Start the session
Give provideSignalR the base URL of your server and a function that returns the access token:
export const appConfig: ApplicationConfig = {
providers: [
provideSignalR(() => {
const auth = inject(AuthService);
return {
baseUrl: "https://api.example.com",
accessTokenFactory: () => auth.token(),
enabled: auth.isLoggedIn,
onError: (hub, error, info) => console.error(hub, info.source, error),
};
}),
],
};Use a hub in a component
This component shows the messages of a room and sends one message:
import { Component, effect, signal } from "@angular/core";
import { injectHubEvent, injectHubInvoke, injectHubStatus } from "./client";
import { ignoreAbort } from "./ignore-abort";
import type { ChatMessage } from "./contract";
@Component({
selector: "app-room",
template: `
<ul>
@for (message of messages(); track $index) {
<li>{{ message.user }}: {{ message.text }}</li>
}
</ul>
<button [disabled]="status() !== 'connected'" (click)="send()">Say hello</button>
`,
})
export class RoomComponent {
roomId = "general";
messages = signal<ChatMessage[]>([]);
status = injectHubStatus("/hubs/rooms");
private joinRoom = injectHubInvoke("/hubs/rooms", "JoinRoom");
private sendMessage = injectHubInvoke("/hubs/rooms", "SendMessage");
constructor() {
effect(() => {
if (this.status() === "connected") this.joinRoom(this.roomId).catch(ignoreAbort);
});
injectHubEvent("/hubs/rooms", "MessageReceived", (message) => {
if (message.roomId === this.roomId) this.messages.update((list) => [...list, message]);
});
}
send() {
this.sendMessage(this.roomId, "Hello").catch(ignoreAbort);
}
}ignoreAbort ignores the AbortError that a call gets when the component unmounts while the call waits. See Calls.
Run the example app
The repository has a runnable example app with a matching server.
Open examples/angular.
Next, read Client config and Connection status.