AngularAtlasAusg. 22.2

Kap. 15Echtzeit & offline65 / 75

WebSockets: Echtzeit, die das Netz übersteht

Ein typisierter Socket als Signals: Reconnect mit Backoff und Jitter, Heartbeat, ein Postausgang im Offline-Modus und SSR-Sicherheit.

Architektur ~0 Min. Lesezeit

HTTP ist eine Frage und danach eine Antwort. Ein WebSocket ist eine Leitung, die offen bleibt, und der Server darf zuerst sprechen: eine neue Chatnachricht, ein geänderter Preis, ein fertiger Build. Die Leitung zu öffnen ist eine Codezeile. Sie im Zug, auf einem Laptop im Ruhezustand oder während eines Server-Deploys am Leben zu halten, ist die eigentliche Arbeit.

Diese Seite baut einen typisierten Socket, der sich selbst neu verbindet und als Signals bereitsteht: Verbindungsstatus, Nachrichten, ein Postausgang für alles, was du offline sendest, und ein Reconnect, der den Server nicht gleich wieder umwirft, sobald er zurück ist.

Warum das wichtig ist
Sockets sterben in Produktion ständig: das WLAN wechselt das Netz, Proxys schließen inaktive Verbindungen nach einer Minute, Deploys starten Server neu. Ein Socket ohne Reconnect sieht in der Demo perfekt aus und hört dann in den Händen der Nutzer still auf, sich zu aktualisieren — ohne jede Fehlermeldung.

Das Minimum: webSocket() aus RxJS

RxJS bringt ein Subject für WebSockets mit: subscribe() öffnet die Verbindung, next() sendet, unsubscribe() schließt sie. Nachrichten sind standardmäßig in beide Richtungen JSON (serializer und deserializer ändern das).

import { webSocket } from 'rxjs/webSocket';

interface ChatMessage {
  type: 'chat';
  user: string;
  text: string;
}

const socket = webSocket<ChatMessage>('wss://api.example.com/live');

// subscribe() öffnet die Verbindung; jede Nachricht kommt schon geparst an (JSON.parse)
const sub = socket.subscribe({
  next: (msg) => console.log(msg.user, msg.text),
  error: (err) => console.error('Verbindung verloren', err), // der Stream ENDET hier
  complete: () => console.log('vom Server sauber geschlossen'), // ...oder hier
});

socket.next({ type: 'chat', user: 'ana', text: 'hi' }); // gesendet als JSON.stringify(...)

sub.unsubscribe(); // schließt die Verbindung

Zwei Details zählen später. Der Stream endet, wenn die Verbindung abbricht: mit error, wenn es abnormal war, mit complete, wenn der Server sauber geschlossen hat. Und was du sendest, bevor die Verbindung offen ist, wird gepuffert und beim Verbinden gesendet.

Eine Verbindung, die zurückkommt: Backoff mit Jitter

Wenn die Leitung abbricht, ist sofortiges Wiederholen in einer Schleife die schlechteste Wahl. Wurde der Server gerade neu gestartet, treffen ihn alle Clients in derselben Sekunde — die Thundering Herd — und er fällt erneut um. Das übliche Rezept: nach jedem Fehlschlag länger warten (exponentieller Backoff), die Wartezeit deckeln und Zufall hinzufügen (Jitter), damit sich die Clients verteilen.

/**
 * „Full Jitter“: eine zufällige Wartezeit zwischen 0 und min(cap, base · 2^attempt).
 * Versuch 1 → 0–1 s, 2 → 0–2 s, 3 → 0–4 s ... nie mehr als 10 s.
 */
export function fullJitter(attempt: number, base = 500, cap = 10_000): number {
  return Math.random() * Math.min(cap, base * 2 ** attempt);
}
Live200 Clients verbinden sich nach einem Server-Neustart neu
Server zurück 0 s 10 s 20 s 30 s 40 s

fehlgeschlagener Versuchwieder verbunden

Spitze nach Neustart
200
Versuche insgesamt
2200
wieder online
200 / 200
der Letzte zurück bei
10.2 s
Vergleiche die drei Strategien. Achte auf die Spitze: wie viele Clients den Server in derselben halben Sekunde treffen, direkt nachdem er zurück ist.

Fest 1 s hämmert auf den Server ein, solange er unten ist. Exponentiell ohne Jitter sendet weniger Anfragen, aber in synchronen Wellen: Wer zur selben Zeit gescheitert ist, versucht es zur selben Zeit erneut. Full Jitter verteilt die Rückkehr über mehrere Sekunden, der Server bekommt ein Rinnsal statt einer Wand; der Preis ist, dass die unglücklichsten Clients ein paar Sekunden später zurückkommen. Die Obergrenze hält diese Wartezeit vernünftig. Für einen Socket ist das fast immer der richtige Tausch.

retry() reicht nicht: saubere Schließungen

retry() reagiert nur auf Fehler. Ein Server, der höflich neu startet, schließt Verbindungen mit Code 1001 oder 1012 („service restart“) und wasClean: true — und dann endet der Stream regulär, ohne Fehler. Ohne repeat() bleibt deine App genau während der Deploys getrennt. Setze den Versuchszähler in openObserver zurück, wenn die Leitung wirklich offen ist, nicht bei der ersten Nachricht.

import { repeat, retry, timer } from 'rxjs';
import { webSocket } from 'rxjs/webSocket';
import { fullJitter } from './backoff';
import type { ServerMessage } from './live-socket';

let attempt = 0;
const wait = () => timer(fullJitter(++attempt));

const socket = webSocket<ServerMessage>({
  url: 'wss://api.example.com/live',
  openObserver: { next: () => (attempt = 0) }, // zurücksetzen, wenn die Leitung wirklich offen ist
});

socket
  .pipe(
    retry({ delay: wait }), // Netzwerkfehler, abnormales Schließen (1006)
    repeat({ delay: wait }), // SAUBERES Schließen (1001, 1012 „service restart“): der Stream endet regulär!
  )
  .subscribe((msg) => console.log(msg));
Der Puffer, auf den du dich nicht verlassen kannst
WebSocketSubject behält, was du sendest, bevor der Socket offen ist, ersetzt diesen Puffer aber bei jedem fehlgeschlagenen Versuch (_resetState() im RxJS-Quellcode). Nachrichten, die bei ausgefallenem Netz gesendet werden, sind verloren, sobald ein einziger Reconnect-Versuch scheitert. Wenn sie wichtig sind, führe einen eigenen Postausgang und leere ihn in openObserver.

Der Socket als Service mit Signals

Komponenten sollten nichts über Backoff oder JSON wissen. Ein Service hält eine Verbindung für die ganze Anwendung, stellt Status und Nachrichten als Signals bereit und bietet ein einziges send(). Solange die Leitung weg ist, warten Nachrichten in einem begrenzten Postausgang und gehen nach dem Reconnect der Reihe nach raus.

import { DestroyRef, Service, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval, repeat, retry, timeout, timer } from 'rxjs';
import { webSocket } from 'rxjs/webSocket';
import { fullJitter } from './backoff';

export type ServerMessage =
  | { type: 'chat'; user: string; text: string }
  | { type: 'presence'; online: number }
  | { type: 'pong' };
export type ClientMessage = { type: 'chat'; text: string } | { type: 'ping' };

export type SocketStatus = 'idle' | 'connecting' | 'open' | 'reconnecting';

const PING_EVERY = 15_000;
const DEAD_AFTER = 20_000; // 20 s lang gar keine Nachricht = tote Verbindung

/** Eine einzige Verbindung für die ganze App, als Signals bereitgestellt. */
@Service()
export class LiveSocket {
  private readonly destroyRef = inject(DestroyRef);

  readonly status = signal<SocketStatus>('idle');
  readonly online = signal(0);
  readonly messages = signal<readonly ServerMessage[]>([]);
  /** Was gesendet wurde, während die Leitung weg war; es geht beim nächsten „open“ der Reihe nach raus. */
  readonly outbox = signal<readonly ClientMessage[]>([]);

  private attempt = 0;
  // RxJS nutzt einen einzigen Typ für beide Richtungen
  private readonly socket = webSocket<ServerMessage | ClientMessage>({
    url: 'wss://api.example.com/live',
    openObserver: { next: () => this.opened() },
    closeObserver: { next: () => this.status.set('reconnecting') },
  });

  connect(): void {
    if (this.status() !== 'idle') return;
    this.status.set('connecting');
    const wait = () => timer(fullJitter(++this.attempt));

    this.socket
      .pipe(
        timeout({ each: DEAD_AFTER }), // Heartbeat: Stille → Fehler → retry
        retry({ delay: wait }),
        repeat({ delay: wait }),
        takeUntilDestroyed(this.destroyRef),
      )
      .subscribe((msg) => this.receive(msg as ServerMessage));

    interval(PING_EVERY)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => {
        if (this.status() === 'open') this.socket.next({ type: 'ping' });
      });
  }

  send(msg: ClientMessage): void {
    if (this.status() === 'open') this.socket.next(msg);
    else this.outbox.update((list) => [...list, msg].slice(-100)); // begrenzt!
  }

  private opened(): void {
    this.status.set('open');
    this.attempt = 0;
    const pending = this.outbox();
    this.outbox.set([]);
    pending.forEach((msg) => this.socket.next(msg));
  }

  private receive(msg: ServerMessage): void {
    if (msg.type === 'presence') this.online.set(msg.online);
    else if (msg.type === 'chat') this.messages.update((list) => [...list, msg].slice(-200));
  }
}
LiveEine Live-Verbindung: Netz kappen, Server neu starten
nicht verbunden0 online
  1. Schreib eine Nachricht: Der Server schickt sie an alle Verbundenen zurück.
  1. Hier erscheinen die Ereignisse der Verbindung.
Kappe das Netz, sende zwei Nachrichten und stelle es wieder her: Sie gehen nach dem Reconnect der Reihe nach raus. Probiere dann „Stiller Abbruch“: Die Verbindung bleibt offen, aber nichts kommt mehr durch.

Die Demo führt den Code oben mit kürzeren Intervallen aus. Nur WebSocketCtor ist ersetzt: Statt eines echten WebSocket bekommt sie einen Server im Speicher, der neu starten, das Netz verlieren oder eine Verbindung einfrieren kann.

Heartbeat: eine tote Verbindung, die lebendig aussieht

Eine Verbindung kann ohne jedes Close-Ereignis sterben: Der Laptopdeckel geht zu, ein NAT vergisst die Zuordnung, das Mobilnetz wechselt die Zelle. TCP merkt das unter Umständen minutenlang nicht, und readyState sagt weiterhin OPEN. Die Abhilfe ist ein Heartbeat: Der Client sendet in festem Abstand ein ping, und timeout({ each }) behandelt jede Stille über dem Limit als tote Leitung. Der Fehler geht an retry(), das eine frische Verbindung öffnet.

Was du auf einer toten, aber offenen Leitung sendest, geht ohne Fehler verloren: send() klappt, die Daten kommen nie an. Für Nachrichten, die ankommen müssen (eine Bestellung, eine Zahlung), gib jeder eine ID, warte auf ein ack vom Server und sende nach dem Reconnect erneut, was nicht bestätigt wurde. Der Server ignoriert Duplikate anhand der ID.

SSR, Authentifizierung, Tabs und Resync

  • SSR und Prerendering. Der Code läuft auch auf dem Server, wo es keinen Socket gibt, den man öffnen sollte. Verbinde dich in afterNextRender (oder hinter isPlatformBrowser) und lass das Server-Rendering einen leeren Zustand zeigen. Siehe serverkompatibler Code.
  • Authentifizierung. Der Browser lässt dich keinen Authorization-Header auf einen WebSocket setzen. Optionen: ein Session-Cookie (gleiche Site), ein kurzes Einmal-Ticket per HTTP in der URL oder eine erste auth-Nachricht. Kein langlebiges Token in die URL — URLs landen in Serverlogs. Tokens und Refresh behandelt Authentifizierung: Tokens & Refresh.
  • Mehrere Tabs. Jeder Tab öffnet seine eigene Verbindung. Wenn das ein Problem ist (Verbindungslimits, doppelte Benachrichtigungen), kann ein Tab den Socket besitzen und Nachrichten über BroadcastChannel oder einen SharedWorker an die anderen weitergeben.
  • Resync nach dem Reconnect. Während du weg warst, hat der Server Dinge gesendet, die du nie bekommen hast. Fordere nach jedem open das Verpasste an (since=lastEventId) oder lade den Zustand per HTTP neu. Änderungen, die du zwischendurch gemacht hast, sind Thema von optimistischen Updates.
import { HttpClient } from '@angular/common/http';
import { defer, retry, switchMap, timer } from 'rxjs';
import { webSocket } from 'rxjs/webSocket';
import { fullJitter } from './backoff';
import type { ServerMessage } from './live-socket';

// Der Browser kann bei einem WebSocket keinen Authorization-Header setzen.
// 1) ein kurzes Ticket per HTTP (dein Auth-Interceptor fügt das Token wie gewohnt hinzu)
// 2) das Ticket ist einmalig und lebt ~30 s, darf also in die URL
// 3) defer() → jeder Reconnect holt ein NEUES Ticket
export function authenticatedSocket(http: HttpClient) {
  return defer(() => http.post<{ ticket: string }>('/api/ws-ticket', {})).pipe(
    switchMap(({ ticket }) => webSocket<ServerMessage>(`wss://api.example.com/live?ticket=${ticket}`)),
    retry({ delay: (_, n) => timer(fullJitter(n)) }),
  );
}

WebSocket oder Server-Sent Events?

Wenn nur der Server spricht — Benachrichtigungen, ein Feed, der Fortschritt eines Jobs, Text Token für Token —, sind Server-Sent Events einfacher: schlichtes HTTP, im Browser eingebauter Reconnect und die ID des letzten Ereignisses wird automatisch zurückgeschickt.

WebSocketServer-Sent Events
Richtungin beide RichtungenServer → Browser
Protokolleigenes (ws:// / wss://, nach einem HTTP-Upgrade)schlichtes HTTP (text/event-stream)
Reconnectmusst du selbst schreibeneingebaut, mit Last-Event-ID
DatenText und Binärnur Text
Typischer EinsatzChat, gemeinsames Bearbeiten, Spiele, Handel in beide RichtungenBenachrichtigungen, Live-Feeds, Job-Fortschritt, KI-Textstreaming
import { Observable } from 'rxjs';

/** Server-Sent Events: nur Server → Browser, schlichtes HTTP, Reconnect eingebaut. */
export function serverEvents<T>(url: string): Observable<T> {
  return new Observable<T>((subscriber) => {
    const source = new EventSource(url, { withCredentials: true });
    source.onmessage = (e) => subscriber.next(JSON.parse(e.data) as T);
    source.onerror = () => {
      // der Browser verbindet sich selbst neu (und sendet Last-Event-ID); nur CLOSED ist endgültig
      if (source.readyState === EventSource.CLOSED) subscriber.error(new Error('SSE closed'));
    };
    return () => source.close();
  });
}

Wann ja, wann nein

WebSocket
  • Beide Seiten sprechen oft: Chat, Präsenz, gemeinsames Bearbeiten.
  • Latenz zählt, und die Nachrichten sind klein und häufig.
  • Du brauchst Binärdaten oder ein eigenes Protokoll über die Verbindung.
Etwas anderes
  • Nur der Server spricht: Server-Sent Events sind einfacher. Ab und zu eine Anfrage: schlicht HttpClient.
  • Daten, die sich alle paar Minuten ändern: Polling alle 30–60 s ist billiger und viel einfacher zu betreiben.
  • Änderungen, die offline funktionieren und später synchronisiert werden müssen: Das ist ein Offline-first-Problem, kein Socket-Problem.

Häufige Stolperfallen

  • Ein Socket pro Komponente. Fünf Widgets, fünf Verbindungen zum selben Server. Halte die Verbindung in einem Root-Service.
  • Retry ohne Jitter oder ohne Obergrenze. Tausende Clients kommen in synchronen Wellen zurück, oder ein Client wartet nach einem langen Ausfall 17 Minuten.
  • Nur retry(), kein repeat(). Saubere Schließungen (jeder Deploy) beenden den Stream für immer.
  • Sich auf den eingebauten Puffer verlassen. Offline gesendete Nachrichten verschwinden nach dem ersten fehlgeschlagenen Versuch. Nutze einen eigenen, begrenzten Postausgang.
  • Kein Heartbeat. Der Status sagt „verbunden“, aber seit zehn Minuten kam nichts mehr.
  • Verbinden während SSR oder ein langes JWT in der URL. Das Erste öffnet Sockets vom Server, das Zweite hinterlässt Tokens in Logs.

Spickzettel

webSocket<T>(url | config)        subscribe = verbinden · next() = senden · unsubscribe = schließen
vor dem ersten „open“             next() wird gepuffert und beim Verbinden gesendet
...aber nach einem Fehlversuch    wird der Puffer verworfen → eigenen Postausgang führen
Netzwerkfehler / Code 1006        retry({ delay })
sauberes Schließen (1000/1001/1012) repeat({ delay })   der Stream ENDET regulär, ohne Fehler
Backoff                           random(0, min(cap, base · 2^n))   „Full Jitter“
Backoff zurücksetzen              in openObserver, nicht bei der ersten Nachricht
tot, aber nicht geschlossen       Ping alle 15 s + timeout({ each: 20_000 })
SSR / Prerender                   in afterNextRender verbinden, nie im Konstruktor
Auth                              Cookie · kurzes Einmal-Ticket · erste „auth“-Nachricht
nach dem Reconnect                Resync: das Verpasste anfordern (since=lastEventId)
Nachrichten, die ankommen müssen  ID + Ack vom Server, Unbestätigtes erneut senden
nur eine Richtung?                Server-Sent Events (EventSource): Reconnect ist eingebaut

Kap. 15 · 0 von 3 abgeschlossen

Lektion abgeschlossen?

Markiere sie, dann erscheint sie auf deiner Karte: im Inhaltsverzeichnis und auf der Startseite.

Wird automatisch markiert, sobald du die ganze Lektion gelesen hast.

  1. WebSockets: Echtzeit, die das Netz übersteht
  2. Optimistische Updates & Rollback
  3. Offline-first: Service Worker & Caching
Deine ganze Karte ansehen

Alle 75 Seiten · tippen zum Filtern

  • 01Was ist neu in Angular 21 & 22Alle wichtigen Änderungen in v21 und v22 in Kürze, mit Link zur Seite, die sie jeweils erklärt.Start
  • 01Deine erste App, Schritt für SchrittEine Leseliste, von einer leeren Komponente bis zur fertigen App — Signals, @for, Inputs und Outputs, ein Service und ein Formular, Schritt für Schritt.Start
  • 01Schnelle Entscheidungen„Wohin damit?“, „Signal oder Observable?“, „Shared oder Feature?“ — kurze Antworten auf Alltagsfragen.Start
  • 01Angular-Interviewfragen60 echte Interviewfragen für Junior, Mid und Senior, mit der passenden Antwort, der Nachfrage und den Warnsignalen. Dazu ein Probeinterview.Start
  • 02Standalone & BootstrapWie eine App ohne NgModule startet: bootstrapApplication, app.config.ts, imports in der Komponente.Moderne Grundlagen
  • 02Data Binding & EventsInterpolation, Property- und Attribut-Bindings, Events mit $event und Tastenfiltern und wie [( )] aufgebaut ist — was in das DOM geht und was zurückkommt.Moderne Grundlagen
  • 02Control Flow: @if, @for, @switchDie Block-Syntax, die *ngIf/*ngFor/ngSwitch ersetzt, mit track, @empty und Exhaustive Checks.Moderne Grundlagen
  • 02@let und die neuen Template-AusdrückeLokale Variablen mit @let, Arrow Functions, Spread, Regex, instanceof, Kommentare in Tags.Moderne Grundlagen
  • 02@defer — Lazy Loading in TemplatesSchwere Komponenten nur laden, wenn sie gebraucht werden: Trigger, Prefetch, Placeholder, Loading, Error.Moderne Grundlagen
  • 02Class- & Style-BindingsWarum [class.x] und [style.x] ngClass und ngStyle abgelöst haben (mit automatischer Migration ab v21).Moderne Grundlagen
  • 02Komponenten-Styles & Kapselung:host, die drei Kapselungsmodi, Theming mit CSS-Variablen und warum ::ng-deep der letzte Ausweg ist.Moderne Grundlagen
  • 02Pipes: wann und wiePure vs. Impure Pipes, eigene Pipes und wie du zwischen Pipe, computed() und Methode wählst.Moderne Grundlagen
  • 03signal() und computed()Die reaktiven Grundbausteine: beschreibbarer Zustand, abgeleiteter Zustand, Gleichheit, untracked und Regeln zur Immutability.Signals & Reaktivität
  • 03effect() — wann ja, wann neinMit Signals synchronisierte Seiteneffekte: localStorage, Logging, imperative APIs. Und ihre Stolperfallen.Signals & Reaktivität
  • 03linkedSignal() — editierbarer abgeleiteter ZustandEin Signal, das sich zurücksetzt, wenn sich seine Quelle ändert, das du aber auch manuell setzen kannst.Signals & Reaktivität
  • 03resource, rxResource, httpResourceAsynchrone Daten als Signals: status, value, error, reload, automatischer Abbruch. Stabil seit v22.Signals & Reaktivität
  • 03debounced() — verzögerte SignalsDie „Debounce“-Variante eines Signals, ohne RxJS. Perfekt für die Suche.Signals & Reaktivität
  • 03Listen: suchen, filtern, sortieren, blätternEine Kette von computed-Signals, die nur neu berechnet, was sich geändert hat — mit den Filtern in der URL, damit ein Link dieselbe Ansicht öffnet.Signals & Reaktivität
  • 03Observables von null anWas ein Observable ist, subscribe und unsubscribe, die Operatoren, die du jede Woche brauchst, cold vs. hot — und wann ein Signal reicht.Signals & Reaktivität
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — und wann RxJS weiterhin die richtige Wahl bleibt.Signals & Reaktivität
  • 04input() und output()Inputs als Signals (required, transform, alias) und typisierte Outputs. Daten nach unten, Events nach oben.Komponenten: Kommunikation
  • 04model() — Two-Way-BindingBanana-in-a-Box [( )] mit einer einzigen API. Wann du es nutzt und wann input + output besser ist.Komponenten: Kommunikation
  • 04viewChild & contentChildQueries als Signals: viewChild, viewChildren, contentChild, contentChildren, die Option read.Komponenten: Kommunikation
  • 04Lifecycle & DestroyRefWelche Hooks in der Signals-Ära noch zählen, afterNextRender, DestroyRef und typisiertes ngOnChanges.Komponenten: Kommunikation
  • 04Content Projectionng-content mit select, mehrere Slots, Standardinhalt und die Falle der bedingten Projektion.Komponenten: Kommunikation
  • 04ng-template & ngTemplateOutletTemplates als Werte: Render Props in Angular, typisierter Kontext und Anpassen von Komponenten.Komponenten: Kommunikation
  • 05Smart vs. DumbContainer vs. Presentational: wer den Zustand hält, wer nur anzeigt und wie du die beiden sauber trennst.Komponenten-Design
  • 05Generische Komponenten <T>Eine korrekt typisierte DataTable<T> oder Select<T>, mit Templates für Zellen.Komponenten-Design
  • 05Dynamische KomponentenNgComponentOutlet, createComponent mit inputBinding/outputBinding und ein Dashboard, das aus einer Konfiguration gebaut wird.Komponenten-Design
  • 05Direktiven & Host-KompositionAttribut-Direktiven, die host: {}-Metadaten und die Directive Composition API (hostDirectives).Komponenten-Design
  • 05Eigene Formular-ControlsFormValueControl (Signal Forms) vs. ControlValueAccessor: eine Sternebewertung, in beiden Varianten.Komponenten-Design
  • 05Change DetectionOnPush ist ab v22 der Standard, zone.js entfällt ab v21. Was das Rendering auslöst und wie du dir nicht die Finger verbrennst.Komponenten-Design
  • 06inject() & @Service()Der neue @Service()-Decorator ab v22, inject() vs. Konstruktor und der Injection-Kontext.Dependency Injection
  • 06Provider & die Injector-HierarchieRoot vs. Route vs. Komponente, useClass/useValue/useFactory/useExisting und die Modifier optional/self/skipSelf.Dependency Injection
  • 06InjectionToken & provideX()Typisierte Konfiguration, provideFeature()-Funktionen, makeEnvironmentProviders und Initializer.Dependency Injection
  • 06injectAsync() — Lazy ServicesLade einen schweren Service erst, wenn du ihn brauchst, mit Prefetch im Leerlauf.Dependency Injection
  • 07Ordnerstrukturcore / shared / features, was in welchen Ordner gehört und wie ein Feature von innen aussieht.Architektur der Anwendung
  • 07Shared vs. FeatureWann du eine Komponente nach Shared verschiebst und (vor allem) wann NICHT. Eine konkrete Checkliste.Architektur der Anwendung
  • 07Interfaces & ModelleInline-Interface oder eigene Datei? DTO vs. Domänenmodell, type vs. interface, wohin damit.Architektur der Anwendung
  • 07State Management mit SignalsEinfache Signal-Stores, Fassaden, lokaler vs. globaler Zustand und wann du NgRx brauchst.Architektur der Anwendung
  • 07Styleguide & NamensgebungDie aktuellen offiziellen Konventionen: Dateinamen ohne Suffixe, protected, readonly, Reihenfolge der Member.Architektur der Anwendung
  • 07Barrels, Aliase & Grenzenindex.ts, Pfad-Aliase, die öffentliche API eines Features und Abhängigkeitsregeln, die sich automatisch prüfen lassen.Architektur der Anwendung
  • 07Fehler: @boundary & ErrorHandlerDer neue @boundary-Block ab v22.2, der globale ErrorHandler, HTTP-Fehler und Fehler aus resource.Architektur der Anwendung
  • 08Modernes Routing & Lazy LoadingloadComponent, Child Routes, Parameter als Inputs und Parameter-Vererbung (neuer Standard in v22).Routing
  • 08Guards & ResolvercanActivate, canMatch, canDeactivate und resolve als Funktionen, mit Redirects über UrlTree / RedirectCommand.Routing
  • 09Signal Forms — die GrundlagenFormulare auf Basis von Signals: form(), [formField], schemabasierte Validierung. Stabil seit v22.Formulare
  • 09Signal Forms — fortgeschrittenEigene und feldübergreifende Validierung, async/HTTP mit Debounce, dynamische Listen, disabled/hidden, wiederverwendbare Schemas.Formulare
  • 09Typisierte Reactive FormsTypisierte FormGroup, nonNullable, FormArray — und wie sie neben Signal Forms bestehen.Formulare
  • 10HttpClient, Interceptors & die API-SchichtHttpClient ohne Setup (v21), Fetch als Standard (v22), funktionale Interceptors und eine API-Schicht mit DTO-Mapping.HTTP & Daten
  • 11Performance — eine ChecklisteWas wirklich zählt: OnPush + Signals, track, @defer, Lazy Routes, Bilder, Bundles und Hydration.Qualität & Performance
  • 11Testen mit VitestVitest ist ab v21 der Standard-Runner. TestBed, Komponenten mit Signals, Services, HTTP und Harnesses.Qualität & Performance
  • 11DevTools: ein Blick in deine AppChrome DevTools und Angular DevTools Schritt für Schritt: was jedes Panel zeigt, mit Übungen, die auf dieser Website funktionieren.Qualität & Performance
  • 11Angular AriaHeadless, barrierefreie Komponenten, die du nach Belieben stylst: Tabs, Accordion, Menü, Listbox...Qualität & Performance
  • 12SSR, Prerendering & Render ModesWann auf dem Server gerendert wird, wann beim Build und wann nur im Browser: RenderMode pro Route, app.routes.server.ts und die echten Kosten.SSR & Hydration
  • 12Hydration: Event Replay & inkrementellWie der Browser das Server-HTML übernimmt, ohne es neu zu rendern: vollständige Hydration, Event Replay, @defer (hydrate on …) und Mismatch-Fehler.SSR & Hydration
  • 12Servertauglicher Codewindow gibt es auf dem Server nicht: PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, der HTTP-Cache und PendingTasks.SSR & Hydration
  • 13Micro FrontendsWann es sich lohnt, eine App in mehrere Deployments aufzuteilen und wann es nur Komplexität ist: Native Federation, Module Federation, iframes, Web Components.Architektur im großen Maßstab
  • 13Web Components mit Angular ElementscreateCustomElement: eine Angular-Komponente, die überall als HTML-Tag nutzbar ist, plus Kommunikation zwischen Apps über Events und BroadcastChannel.Architektur im großen Maßstab
  • 13Monorepo, Bibliotheken & GrenzenEin Workspace mit mehreren Apps und Bibliotheken: ng generate library, Nx, Secondary Entry Points, Publishing und Abhängigkeitsregeln.Architektur im großen Maßstab
  • 13Hexagonale Architektur (Ports & Adapters)Eine Domäne ohne Angular, Ports als abstrakte Klassen und austauschbare Adapter: HTTP, localStorage oder In-Memory, ohne die UI anzufassen.Architektur im großen Maßstab
  • 14Signals unter der HaubeDer reaktive Graph: Producer und Consumer, Push-Pull, warum computed lazy und glitch-frei ist, das Diamond-Problem und wann Effects laufen.Fortgeschrittene Reaktivität
  • 14Fortgeschrittenes RxJS: Operatoren höherer OrdnungswitchMap, mergeMap, concatMap und exhaustMap an einem Live-Diagramm, dazu Retry mit Backoff, shareReplay und wie du Leaks vermeidest.Fortgeschrittene Reaktivität
  • 14NgRx SignalStoreState Management im großen Stil auf Signals: withState, withComputed, withMethods, rxMethod, Entities und Hooks — und wann ein einfacher Store reicht.Fortgeschrittene Reaktivität
  • 14Zustandsautomaten mit SignalsKeine unmöglichen Zustände mehr: ein typisierter Automat auf Signals, mit Übergängen, Guards und einem Live-Zustandsdiagramm.Fortgeschrittene Reaktivität
  • 15WebSockets: Echtzeit, die das Netz überstehtEin typisierter Socket als Signals: Reconnect mit Backoff und Jitter, Heartbeat, ein Postausgang im Offline-Modus und SSR-Sicherheit.Echtzeit & offline
  • 15Optimistische Updates & RollbackEine sofortige UI, die ehrlich bleibt: Snapshot und Rollback, Antworten in falscher Reihenfolge, Idempotenzschlüssel.Echtzeit & offline
  • 15Offline-first: Service Worker & CachingDer Angular Service Worker: Caching-Strategien, Update-Hinweise und ein Postausgang für Änderungen, die offline entstehen.Echtzeit & offline
  • 16Web WorkersSchwere Berechnungen, ohne die UI zu blockieren: ein echter Worker in der Seite, Message Passing und wann es sich nicht lohnt.Fortgeschrittene UI & Performance
  • 16Riesige Listen: Virtual Scroll100.000 Zeilen ohne Ruckeln: CDK Virtual Scroll, itemSize, trackBy und die Alternativen (Paginierung, Infinite Scroll).Fortgeschrittene UI & Performance
  • 16Moderne Animationen & View Transitionsanimate.enter und animate.leave statt des veralteten @angular/animations, CSS-Transitions und View Transitions bei der Navigation.Fortgeschrittene UI & Performance
  • 16Memory Leaks finden und behebenSubscriptions, Timer und Listener, die ihre Komponente überleben – wie du sie in den DevTools findest und schließt.Fortgeschrittene UI & Performance
  • 17Sicherheit: XSS, Sanitizing & CSPWas Angular automatisch gegen XSS tut, wann bypassSecurityTrust gefährlich wird, dazu CSP mit Nonce, Trusted Types und XSRF.Sicherheit & Globalisierung
  • 17Authentifizierung: Tokens & RefreshEin Interceptor, der das Token anhängt, bei 401 einmal erneuert und die wartenden Requests wiederholt — dazu, wo du das Token aufbewahrst.Sicherheit & Globalisierung
  • 17OAuth 2.1 & OpenID Connect mit PKCEDer Authorization-Code-Flow mit PKCE Schritt für Schritt: Verifier und Challenge, State und Nonce, das ID-Token und warum ein BFF sicherer ist.Sicherheit & Globalisierung
  • 17Internationalisierung (i18n)$localize, das i18n-Attribut, ICU plural/select, ein Build pro Sprache vs. Übersetzungen zur Laufzeit und Formatierung nach Locale.Sicherheit & Globalisierung