WebSockets bieten eine bidirektionale, voll-duplexe Kommunikation zwischen Client und Server über eine dauerhafte Verbindung. Im Gegensatz zu HTTP-Anfragen ermöglichen WebSockets kontinuierliche Verbindungen für Echtzeitdaten.
Vorteile: - Echtzeitkommunikation ohne wiederholte Anfragen - Geringere Latenz und reduzierter Overhead - Bidirektionale Kommunikation
Typische Anwendungsfälle: - Chat-Anwendungen - Live-Updates für Dashboards - Multiplayer-Spiele - Kollaborative Editoren - Finanzanwendungen mit Kursdaten
Der einfachste Ansatz ist die Verwendung der nativen WebSocket-API in einem Angular-Service:
// websocket.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class WebSocketService {
private socket: WebSocket;
private messagesSubject = new BehaviorSubject<any[]>([]);
public messages$ = this.messagesSubject.asObservable();
constructor() {}
public connect(url: string): void {
this.socket = new WebSocket(url);
this.socket.onopen = (event) => {
console.log('WebSocket verbunden:', event);
};
this.socket.onmessage = (event) => {
const message = JSON.parse(event.data);
const currentMessages = this.messagesSubject.value;
this.messagesSubject.next([...currentMessages, message]);
};
this.socket.onerror = (event) => {
console.error('WebSocket-Fehler:', event);
};
this.socket.onclose = (event) => {
console.log('WebSocket-Verbindung geschlossen:', event);
};
}
public sendMessage(message: any): void {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(message));
} else {
console.error('WebSocket ist nicht verbunden.');
}
}
public disconnect(): void {
if (this.socket) {
this.socket.close();
}
}
}Verwendung in einer Komponente:
// chat.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { WebSocketService } from './websocket.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-chat',
template: `
<div class="chat-container">
<div class="messages">
<div *ngFor="let msg of messages">{{ msg.text }}</div>
</div>
<div class="input-area">
<input [(ngModel)]="messageText" placeholder="Nachricht eingeben..." />
<button (click)="sendMessage()">Senden</button>
</div>
</div>
`
})
export class ChatComponent implements OnInit, OnDestroy {
messages: any[] = [];
messageText: string = '';
private subscription: Subscription;
constructor(private webSocketService: WebSocketService) {}
ngOnInit() {
this.webSocketService.connect('wss://example.com/chat');
this.subscription = this.webSocketService.messages$.subscribe(messages => {
this.messages = messages;
});
}
sendMessage() {
if (this.messageText.trim()) {
this.webSocketService.sendMessage({ text: this.messageText });
this.messageText = '';
}
}
ngOnDestroy() {
this.webSocketService.disconnect();
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}Für reaktive Anwendungen ist die Integration von RxJS mit WebSockets ideal:
// rxjs-websocket.service.ts
import { Injectable } from '@angular/core';
import { Observable, Subject, BehaviorSubject } from 'rxjs';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { catchError, tap, switchAll, takeUntil } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class RxjsWebSocketService {
private socket$: WebSocketSubject<any>;
private messagesSubject = new Subject<Observable<any>>();
public messages$ = this.messagesSubject.pipe(switchAll());
private connectionStatus = new BehaviorSubject<boolean>(false);
public connectionStatus$ = this.connectionStatus.asObservable();
private destroy$ = new Subject<void>();
public connect(url: string): void {
if (!this.socket$ || this.socket$.closed) {
this.socket$ = webSocket(url);
const messages = this.socket$.pipe(
tap({
next: () => this.connectionStatus.next(true),
error: error => {
console.error('WebSocket-Fehler:', error);
this.connectionStatus.next(false);
},
complete: () => this.connectionStatus.next(false)
}),
takeUntil(this.destroy$),
catchError(error => {
console.error('WebSocket-Fehler, versuche Wiederverbindung:', error);
return this.connect(url);
})
);
this.messagesSubject.next(messages);
}
}
public sendMessage(message: any): void {
if (this.socket$ && !this.socket$.closed) {
this.socket$.next(message);
}
}
public disconnect(): void {
if (this.socket$) {
this.socket$.complete();
this.destroy$.next();
this.connectionStatus.next(false);
}
}
}Socket.IO bietet erweiterte Funktionen wie automatische Wiederverbindung:
npm install socket.io-client @types/socket.io-client// socket-io.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { io, Socket } from 'socket.io-client';
@Injectable({
providedIn: 'root'
})
export class SocketIoService {
private socket: Socket;
connect(url: string, options?: any): void {
this.socket = io(url, options);
}
disconnect(): void {
if (this.socket) {
this.socket.disconnect();
}
}
on<T>(eventName: string): Observable<T> {
return new Observable<T>(observer => {
this.socket.on(eventName, (data: T) => {
observer.next(data);
});
return () => {
this.socket.off(eventName);
};
});
}
emit(eventName: string, data?: any): void {
this.socket.emit(eventName, data);
}
joinRoom(room: string): void {
this.socket.emit('join', room);
}
leaveRoom(room: string): void {
this.socket.emit('leave', room);
}
}Für .NET-Backends ist SignalR eine gute Option:
npm install @microsoft/signalr// signalr.service.ts
import { Injectable } from '@angular/core';
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SignalRService {
private hubConnection: HubConnection;
private connectionStatus = new BehaviorSubject<boolean>(false);
public connectionStatus$ = this.connectionStatus.asObservable();
public createConnection(url: string): void {
this.hubConnection = new HubConnectionBuilder()
.withUrl(url)
.withAutomaticReconnect()
.build();
this.hubConnection.onreconnecting(() => this.connectionStatus.next(false));
this.hubConnection.onreconnected(() => this.connectionStatus.next(true));
this.hubConnection.onclose(() => this.connectionStatus.next(false));
}
public startConnection(): Promise<void> {
return this.hubConnection.start()
.then(() => {
this.connectionStatus.next(true);
})
.catch(err => {
console.error('SignalR-Verbindungsfehler:', err);
throw err;
});
}
public stopConnection(): Promise<void> {
return this.hubConnection.stop()
.then(() => {
this.connectionStatus.next(false);
});
}
public on<T>(methodName: string, callback: (data: T) => void): void {
this.hubConnection.on(methodName, callback);
}
public invoke<T>(methodName: string, ...args: any[]): Promise<T> {
return this.hubConnection.invoke<T>(methodName, ...args);
}
}Ab Angular 16 können Signals für reaktive WebSocket-Integration verwendet werden:
// websocket-signals.service.ts
import { Injectable, signal, computed } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class WebSocketSignalsService {
private socket: WebSocket | null = null;
// Signals
public connected = signal<boolean>(false);
public messages = signal<any[]>([]);
public error = signal<string | null>(null);
// Computed Values
public messageCount = computed(() => this.messages().length);
public lastMessage = computed(() => {
const msgs = this.messages();
return msgs.length > 0 ? msgs[msgs.length - 1] : null;
});
connect(url: string): void {
if (this.socket) {
this.disconnect();
}
this.socket = new WebSocket(url);
this.socket.onopen = () => {
this.connected.set(true);
this.error.set(null);
};
this.socket.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.messages.update(msgs => [...msgs, message]);
} catch (err) {
console.error('Fehler beim Parsen der WebSocket-Nachricht:', err);
}
};
this.socket.onerror = () => {
this.error.set('WebSocket-Fehler aufgetreten');
};
this.socket.onclose = () => {
this.connected.set(false);
};
}
disconnect(): void {
if (this.socket) {
this.socket.close();
this.socket = null;
}
}
sendMessage(message: any): void {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(message));
} else {
this.error.set('Nachricht kann nicht gesendet werden: WebSocket ist nicht verbunden');
}
}
clearMessages(): void {
this.messages.set([]);
}
}ngOnDestroy immer
schließen// chat.component.ts
import { Component, inject, signal, effect, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { WebSocketSignalsService } from './websocket-signals.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-chat',
standalone: true,
template: `
<div class="chat-container">
<header>
<h1>Chat</h1>
<div class="connection-status" [class.connected]="wsService.connected()">
{{ wsService.connected() ? 'Verbunden' : 'Getrennt' }}
</div>
</header>
@defer {
<div class="messages">
@for (message of wsService.messages(); track message.id) {
<div class="message" [class.own]="message.sender === username()">
<strong>{{ message.sender }}</strong>: {{ message.text }}
</div>
} @empty {
<div class="no-messages">Keine Nachrichten</div>
}
</div>
} @loading {
<div class="loading">Lade Nachrichten...</div>
}
<div class="input-area">
<input [formControl]="messageInput" placeholder="Nachricht eingeben..." />
<button (click)="sendMessage()" [disabled]="!wsService.connected()">Senden</button>
</div>
</div>
`
})
export class ChatComponent implements OnDestroy {
wsService = inject(WebSocketSignalsService);
messageInput = new FormControl('');
username = signal<string>('Benutzer' + Math.floor(Math.random() * 1000));
constructor() {
// Verbindung beim Start herstellen
this.wsService.connect('wss://example.com/chat');
// Verbindungsstatus überwachen mit effect()
effect(() => {
if (this.wsService.connected()) {
console.log('Verbindung hergestellt, sende Anmeldung');
this.wsService.sendMessage({
type: 'login',
username: this.username()
});
}
});
}
sendMessage() {
const text = this.messageInput.value?.trim();
if (text && this.wsService.connected()) {
this.wsService.sendMessage({
type: 'message',
sender: this.username(),
text,
timestamp: new Date().toISOString()
});
this.messageInput.setValue('');
}
}
ngOnDestroy() {
this.wsService.disconnect();
}
}WebSockets in Angular bieten eine leistungsstarke Möglichkeit für Echtzeitkommunikation in modernen Webanwendungen. Mit den passenden Tools und Best Practices können robuste, sichere und performante Echtzeitfunktionen implementiert werden.
Für die meisten Anwendungsfälle reicht einer der folgenden Ansätze: - Native WebSockets für einfache Anwendungen - RxJS-WebSockets für reaktive Programmierung - Socket.IO für zusätzliche Features und Browserkompatibilität - SignalR für .NET-Backends - Angular Signals für moderne reaktive Zustandsverwaltung