What is SignalR and Why Use It with Angular?
SignalR is a real-time communications library that simplifies adding bidirectional communication to ASP.NET applications. The library handles transport selection, connection management, and message routing automatically.
How SignalR Works
SignalR establishes a persistent connection between client and server, enabling the server to push data without explicit requests. This is achieved through hubs--high-level communication pipelines that allow remote method calls.
SignalR automatically negotiates the best transport:
- WebSockets: Full-duplex, low-latency communication (preferred)
- Server-Sent Events: Browser fallback when WebSockets unavailable
- Long Polling: Universal fallback with higher latency
For Angular developers, the @microsoft/signalr package provides TypeScript-friendly APIs that align with Angular's patterns.
Key Benefits for Angular Applications
Integrating SignalR with Angular offers several advantages for modern web applications. The reactive programming model with RxJS pairs naturally with SignalR's event-based communication--you can easily expose SignalR connections as Observables, allowing Angular's change detection to automatically update the UI when new data arrives.
For organizations building sophisticated web applications, real-time capabilities are increasingly essential for competitive user experiences. Our /services/web-development/ team specializes in implementing these patterns at scale.
Real-world examples of Angular + SignalR integration:
- Live dashboards: Financial platforms pushing stock updates, analytics dashboards displaying real-time metrics from multiple data sources
- Collaborative applications: Document editors where multiple users see changes instantly, project management tools with live task updates
- Chat and messaging: Customer support chat interfaces, team collaboration tools with instant message delivery
- IoT monitoring: Device telemetry streaming where sensors push data continuously to Angular dashboards
- AI applications: ChatGPT-style streaming responses where token-by-token output appears progressively
The connection resilience built into SignalR, combined with Angular's service-based architecture, enables robust implementations that survive component lifecycle changes and network instability.
When building AI-powered applications with streaming responses, pairing SignalR with modern AI automation services creates powerful user experiences that feel responsive and natural. Learn more about our approach to AI automation services for intelligent, real-time solutions.
Setting Up the Angular SignalR Client
Installation
npm install @microsoft/signalr
Creating a SignalR Service
Best practice in Angular is to encapsulate SignalR connectivity within a service. This approach provides centralized connection management, easy injection into components, clean separation of concerns, and reusability across multiple components.
The following service implementation demonstrates patterns for production-ready real-time communication:
import { Injectable } from '@angular/core';
import * as signalR from '@microsoft/signalr';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class RealTimeDataService {
private connection?: signalR.HubConnection;
private dataStream$ = new BehaviorSubject<any>(null);
private connectionState$ = new BehaviorSubject<boolean>(false);
get dataStream(): Observable<any> {
return this.dataStream$.asObservable();
}
get isConnected(): Observable<boolean> {
return this.connectionState$.asObservable();
}
async connect(hubUrl: string): Promise<void> {
if (this.connection?.state === signalR.HubConnectionState.Connected) {
return;
}
this.connection = new signalR.HubConnectionBuilder()
.withUrl(hubUrl)
.withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
.configureLogging(signalR.LogLevel.Information)
.build();
this.connection.on('DataUpdate', (data: any) => {
this.dataStream$.next(data);
});
this.connection.onreconnecting(() => {
this.connectionState$.next(false);
});
this.connection.onreconnected(() => {
this.connectionState$.next(true);
});
await this.connection.start();
this.connectionState$.next(true);
}
async disconnect(): Promise<void> {
await this.connection?.stop();
this.connectionState$.next(false);
}
invoke(method: string, ...args: any[]): Promise<void> {
return this.connection?.invoke(method, ...args) ?? Promise.resolve();
}
}
This implementation showcases several production-ready patterns: lazy connection initialization to prevent unnecessary startup network traffic, automatic reconnection with configurable backoff intervals, reactive data exposure through BehaviorSubjects for seamless RxJS integration, and centralized event handler registration for maintainable code.
Configuration Options
SignalR provides numerous options to customize connection behavior:
const connection = new signalR.HubConnectionBuilder()
.withUrl(hubUrl, {
transportType: signalR.HttpTransportType.WebSockets,
skipNegotiation: false,
accessTokenFactory: () => this.getAuthToken(),
headers: { 'Custom-Header': 'value' }
})
.withAutomaticReconnect()
.configureLogging(signalR.LogLevel.Warning)
.withHubProtocol(new signalR.JsonHubProtocol())
.build();
Key configuration options include:
- transportType: Specifies which transport to use--WebSockets, Server-Sent Events, or Long Polling. WebSockets provides the lowest latency when available.
- skipNegotiation: When true, skips the negotiation step but requires WebSockets transport. Useful when you know WebSockets is available.
- accessTokenFactory: Function that returns authentication tokens for the connection, enabling seamless integration with your auth system.
- headers: Custom headers to include with each request for specialized authentication or tracking scenarios.
- configureLogging: Controls log verbosity for debugging--use Information during development, Warning or Error in production.
Implementing these patterns correctly requires attention to both client-side and server-side architecture. Our web development team has extensive experience building real-time applications that scale reliably.
1import { Injectable } from '@angular/core';2import * as signalR from '@microsoft/signalr';3import { BehaviorSubject, Observable } from 'rxjs';4 5@Injectable({ providedIn: 'root' })6export class RealTimeDataService {7 private connection?: signalR.HubConnection;8 private dataStream$ = new BehaviorSubject<any>(null);9 private connectionState$ = new BehaviorSubject<boolean>(false);10 11 get dataStream(): Observable<any> {12 return this.dataStream$.asObservable();13 }14 15 get isConnected(): Observable<boolean> {16 return this.connectionState$.asObservable();17 }18 19 async connect(hubUrl: string): Promise<void> {20 this.connection = new signalR.HubConnectionBuilder()21 .withUrl(hubUrl)22 .withAutomaticReconnect([0, 2000, 5000, 10000, 30000])23 .configureLogging(signalR.LogLevel.Information)24 .build();25 26 this.connection.on('DataUpdate', (data: any) => {27 this.dataStream$.next(data);28 });29 30 await this.connection.start();31 this.connectionState$.next(true);32 }33 34 async disconnect(): Promise<void> {35 await this.connection?.stop();36 this.connectionState$.next(false);37 }38 39 invoke(method: string, ...args: any[]): Promise<void> {40 return this.connection?.invoke(method, ...args) ?? Promise.resolve();41 }42}Building SignalR Hubs on the Server
Hub Basics
public class DataHub : Hub {
public async Task Subscribe(string dataType) {
await Groups.AddToGroupAsync(Context.ConnectionId, dataType);
await Clients.Caller.SendAsync("SubscriptionConfirmed", dataType);
}
public async Task BroadcastUpdate(DataUpdate update) {
await Clients.Group(update.Type).SendAsync("DataUpdate", update);
}
}
Hub Lifecycle Methods
SignalR provides several lifecycle hooks for managing hub instances:
- OnConnectedAsync: Called when a new connection is established. Use this to track connections, initialize per-connection state, or add users to default groups. The Context.ConnectionId provides a unique identifier for the connection.
- OnDisconnectedAsync: Called when a connection terminates--whether from user closing the browser, network failure, or explicit disconnect. This is critical for cleaning up resources, removing users from groups, and updating presence status.
- OnReconnected: Called after a successful reconnection. Use this to reestablish any application state that was lost during the disconnection period.
public override async Task OnConnectedAsync() {
await base.OnConnectedAsync();
Console.WriteLine($"Connection established: {Context.ConnectionId}");
}
public override async Task OnDisconnectedAsync(Exception exception) {
await base.OnDisconnectedAsync(exception);
Console.WriteLine($"Connection terminated: {Context.ConnectionId}");
}
Strongly-Typed Hubs
For better type safety and IntelliSense support, implement strongly-typed hubs using interfaces. This approach provides compile-time checking of hub method calls and clearer API contracts between client and server:
public interface IDataHubClient {
Task DataUpdate(DataUpdate update);
Task SubscriptionConfirmed(string dataType);
}
public class DataHub : Hub<IDataHubClient> {
public async Task Subscribe(string dataType) {
await Groups.AddToGroupAsync(Context.ConnectionId, dataType);
await Clients.Caller.SubscriptionConfirmed(dataType);
}
public async Task BroadcastUpdate(DataUpdate update) {
await Clients.Group(update.Type).DataUpdate(update);
}
}
Strongly-typed hubs eliminate string-based method calls, reducing runtime errors and improving the development experience with proper type checking and IDE autocomplete support.
Hubs
High-level communication pipeline for client-server method calls
Groups
Logical groupings of connections for targeted messaging
Connections
Persistent connections with automatic transport selection
Reconnection
Built-in automatic reconnection with configurable backoff
Implementing Real-Time Data Streaming
Server-to-Client Streaming
The most common pattern is pushing updates from the server as they occur--ideal for live dashboards, stock tickers, and monitoring systems. Background services inject IHubContext to broadcast updates to connected clients:
public class StockTickerService : BackgroundService {
private readonly IHubContext<StockHub> _hubContext;
public StockTickerService(IHubContext<StockHub> hubContext) {
_hubContext = hubContext;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
while (!stoppingToken.IsCancellationRequested) {
var update = await GetStockUpdate();
await _hubContext.Clients.Group($"stock_{update.Symbol}")
.SendAsync("StockUpdate", update);
await Task.Delay(1000, stoppingToken);
}
}
}
This pattern enables Angular components to receive streaming updates through their subscribed Observables, updating the UI reactively as new data arrives.
Streaming Large Data Sets
For large data transfers or AI responses, SignalR supports streaming from async enumerables:
public async IAsyncEnumerable<DataChunk> StreamLargeData(
int batchSize,
[EnumeratorCancellation] CancellationToken cancellationToken) {
var offset = 0;
while (!cancellationToken.IsCancellationRequested) {
var batch = await _dataService.GetBatch(offset, batchSize);
foreach (var item in batch) {
yield return item;
}
offset += batchSize;
}
}
AI Streaming Pattern
For AI applications streaming responses token-by-token, similar to ChatGPT, the pattern involves sending partial updates as content is generated. The server sends typing indicators, partial content, and completion signals, while the Angular client accumulates incremental updates:
public async Task StreamAIResponse(string prompt) {
var messageId = Guid.NewGuid().ToString("N");
await Clients.Caller.SendAsync("typing", messageId, true);
await foreach (var partial in _aiService.StreamResponse(prompt)) {
await Clients.Caller.SendAsync("partial", messageId, partial);
}
await Clients.Caller.SendAsync("typing", messageId, false);
await Clients.Caller.SendAsync("completed", messageId);
}
The Angular client accumulates these partial updates, providing the familiar streaming response experience that users expect from modern AI interfaces.
This streaming pattern is particularly powerful when combined with AI automation services, enabling responsive AI-powered interfaces that feel conversational and immediate.
1async askAI(prompt: string): Promise<void> {2 this.partialResponse$.next('');3 this.isComplete$.next(false);4 this.isTyping$.next(true);5 6 await this.connection?.invoke('StreamAIResponse', prompt);7}8 9private setupAIHandlers(): void {10 this.connection?.on('partial', (_id: string, text: string) => {11 this.partialResponse$.next(12 (this.partialResponse$.value || '') + text13 );14 });15 16 this.connection?.on('typing', (_id: string, isTyping: boolean) => {17 this.isTyping$.next(isTyping);18 });19 20 this.connection?.on('completed', (_id: string) => {21 this.isComplete$.next(true);22 this.isTyping$.next(false);23 });24}Managing Connections and State
Connection States
SignalR provides built-in connection state tracking through the HubConnectionState enum: Disconnected (no active connection), Connecting (connection in progress), Connected (active, ready for messages), Reconnecting (attempting to restore connection), and ConnectedLimited (connected with limited functionality).
Automatic Reconnection
Configure retry intervals with exponential backoff for robust network handling:
.withAutomaticReconnect([0, 1000, 5000, 10000, 30000, 60000])
The default intervals are 0, 2, 10, and 30 seconds. Customize these based on your application's tolerance for disconnection--shorter intervals for latency-sensitive applications, longer intervals for battery-sensitive mobile clients.
Handle reconnection events to resubscribe to channels and restore application state:
connection.onreconnecting((error) => {
console.log(`Reconnecting... Error: ${error?.message}`);
});
connection.onreconnected((connectionId) => {
console.log(`Reconnected with ID: ${connectionId}`);
this.resubscribeToChannels();
});
Graceful Disconnection
Handle browser page unload and component cleanup to prevent resource leaks:
@HostListener('window:beforeunload')
onBeforeUnload() {
this.disconnect();
}
ngOnDestroy() {
this.disconnect();
}
Heartbeats
Configure keep-alive intervals for stale connection detection:
services.AddSignalR()
.AddHubOptions<DataHub>(options => {
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
});
The heartbeat interval should balance failure detection speed against server load--15 seconds is a common choice for production environments.
Security Best Practices
Authentication and Authorization
SignalR integrates with ASP.NET Core's authentication system. Use the [Authorize] attribute on hub methods and access Context.User for user information:
[Authorize]
public async Task GetSensitiveData() {
var user = Context.User;
if (user?.Identity?.IsAuthenticated != true) {
throw new HubException("Unauthorized");
}
var data = await _dataService.GetDataForUser(user);
await Clients.Caller.SendAsync("SensitiveData", data);
}
On the Angular client, include authentication tokens:
.withUrl(hubUrl, {
accessTokenFactory: () => this.accessToken
})
CORS Configuration
SignalR requires proper CORS configuration when hosting on different origins, including AllowCredentials:
builder.Services.AddCors(options => {
options.AddPolicy("AllowAngularApp", policy => {
policy.WithOrigins("https://your-app.com")
.AllowCredentials();
});
});
Rate Limiting
Protect your real-time endpoints from abuse with rate limiting:
public class DataHub : Hub {
private static readonly ConcurrentDictionary<string, int>
_requestCounts = new();
[Authorize]
public async Task SendMessage(string message) {
var userId = Context.User?.Identity?.Name;
var count = Interlocked.Increment(ref _requestCounts[userId]);
if (count > 100) {
throw new HubException("Rate limit exceeded");
}
await Clients.All.SendAsync("ReceiveMessage", userId, message);
}
}
Input Validation
Always validate input from SignalR clients to prevent injection attacks:
public async Task SendMessage(string message) {
if (string.IsNullOrWhiteSpace(message)) {
throw new HubException("Message cannot be empty");
}
if (message.Length > 1000) {
throw new HubException("Message too long");
}
var sanitized = System.Web.HttpUtility.HtmlEncode(message);
await Clients.All.SendAsync("ReceiveMessage",
Context.User?.Identity?.Name, sanitized);
}
Security implementation is critical for any web application handling real-time data. Our web development services include comprehensive security architecture for production applications.
Performance Optimization
Message Size Limits
Configure maximum message sizes for your application's needs:
services.AddSignalR()
.AddHubOptions<DataHub>(options => {
options.MaximumReceiveMessageSize = 1024 * 1024; // 1MB
});
Reducing Message Frequency
For high-frequency updates, batch messages using RxJS operators:
private buffer$ = new Subject<StockUpdate>();
private throttled$ = this.buffer$.pipe(
bufferTime(100), // Buffer for 100ms
filter(batch => batch.length > 0),
throttleTime(200) // Emit at most every 200ms
);
Memory Management
Prevent memory leaks by properly cleaning up subscriptions and connections:
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
this.disconnect();
}
Scaling with Azure SignalR
For applications requiring horizontal scaling, use Azure SignalR Service:
builder.Services.AddSignalR()
.AddAzureSignalR();
Azure SignalR handles connection management at scale, removing sticky session requirements and distributing load across multiple server instances.
Monitoring and Diagnostics
Implement comprehensive monitoring with client-side logging and server-side correlation IDs for debugging distributed systems:
const connection = new signalR.HubConnectionBuilder()
.configureLogging({
log: (level, message) => {
if (level === signalR.LogLevel.Error) {
this.logger.error(message);
}
}
})
.build();
// Server-side correlation IDs
public override async Task OnConnectedAsync() {
var correlationId = Guid.NewGuid().ToString();
Context.Items["CorrelationId"] = correlationId;
}
Use correlation IDs to trace requests across client and server logs, making debugging distributed real-time systems significantly easier.
Live Dashboards
Real-time aggregation and display of metrics from multiple data sources
Collaborative Editing
Multi-user document editing with presence awareness and conflict resolution
Notification Systems
Push notifications that inform users without page refreshes
Live Chat
Bidirectional messaging between users in real-time
IoT Data Streaming
Device telemetry streaming with connection persistence
AI Applications
ChatGPT-style streaming responses with typing indicators
Frequently Asked Questions
Conclusion
Integrating SignalR with Angular provides a powerful foundation for building real-time web applications. The key to successful implementation lies in proper architecture: encapsulating SignalR logic within Angular services, implementing comprehensive error handling and reconnection strategies, and following security best practices for authentication and input validation.
Key takeaways from this guide:
-
Encapsulate SignalR in Angular services for clean, testable code with reactive Observable-based APIs
-
Implement robust reconnection strategies with configurable backoff intervals to handle network instability gracefully
-
Follow security best practices including authentication integration, CORS configuration, rate limiting, and input validation
-
Optimize message frequency with RxJS batching when dealing with high-frequency updates to reduce network overhead
-
Consider Azure SignalR for applications requiring horizontal scaling beyond a single server instance
Whether you are building live dashboards, collaborative tools, notification systems, or AI-powered interfaces with streaming responses, these patterns provide a foundation for production-ready real-time applications. Start with simple implementations and gradually add complexity as your understanding of the patterns deepens.
Ready to implement real-time capabilities in your web application? Our team of experienced developers can help you architect and build scalable, secure real-time solutions. Contact our web development team to discuss your project requirements and discover how real-time communication can enhance your user experience.