Imperatives Angular vs. Reaktives Angular – Reifegradmodell
Einordnung
Angular kann imperativ, objektorientiert oder reaktiv genutzt werden.
Die Architekturqualität hängt maßgeblich davon ab, welches Denkmodell dominiert.
Dieses Dokument beschreibt ein Reifegradmodell von:
Imperativem Angular → Reaktivem Angular
Ziel ist nicht Perfektion, sondern Bewusstheit über Architekturentscheidungen.
Überblick – Reifegradstufen
| Stufe | Bezeichnung | Dominantes Paradigma | Architekturmerkmale |
|---|---|---|---|
| 0 | Scripted Angular | Imperativ | Lifecycle-gesteuert, lokale Mutation |
| 1 | Service-Heavy Angular | OOP-dominiert | Logik in Klassen & Services |
| 2 | Observable-Aware Angular | Teil-reaktiv | Streams vorhanden, aber entpackt |
| 3 | Declarative Reactive Angular | Reaktiv | Datenfluss bestimmt UI |
| 4 | Signal-First Angular | Funktional-reaktiv | State-Ableitung statt Mutation |
Stufe 0 – Scripted Angular
Merkmale
ngOnInit()orchestriert alles- viele
.subscribe() - lokale Zustände
- manuelle Unsubscribe-Logik
- imperative Form-Validierung
ngOnInit() {
this.loadUsers();
this.setupForm();
}
Charakteristik
Angular wird wie jQuery mit Klassen verwendet.
UI ist:
ein Objekt, das aktiv Dinge tut.
Risiken
- Race Conditions
- Memory Leaks
- schwer testbar
- Lifecycle-chaotisch
Stufe 1 – Service-Heavy Angular
Merkmale
- Logik wird in Services verschoben
- Komponenten werden dünner
- aber weiterhin imperatives Denken
- Basisklassen entstehen
this.userService.load().subscribe(...)
Charakteristik
OOP dominiert.
State liegt in Services als mutable Felder.
Risiken
- globale Zustände
- versteckte Seiteneffekte
- God Services
- God Base Components
Stufe 2 – Observable-Aware Angular
Merkmale
- Streams werden genutzt
- aber häufig sofort entpackt
- Promise-Konvertierung verbreitet
- doppelte Zustände
users$ = this.userService.users$;
ngOnInit() {
this.users$.subscribe(u => this.users = u);
}
Charakteristik
Reaktivität ist vorhanden,
aber wird imperativ weiterverarbeitet.
Typisches Symptom
Observable existiert nur formal.
Der eigentliche Code ist weiterhin imperativ.
Stufe 3 – Declarative Reactive Angular
Merkmale
- async Pipe
- keine lokalen Spiegelzustände
- ViewModel-Streams (
vm$) - combineLatest / map / switchMap
- keine Lifecycle-Orchestrierung
vm$ = combineLatest([
this.users$,
this.filter$
]).pipe(
map(([users, filter]) => ({
users: users.filter(u => u.active === filter)
}))
);
Template:
<div *ngIf="vm$ | async as vm">
<li *ngFor="let user of vm.users">
Charakteristik
UI ist:
Projektion eines Datenflusses.
Komponenten sind:
- flach
- kompositorisch
- testbar
Stufe 4 – Signal-First Angular
Merkmale
- Signals statt Subjects
- computed statt Getter
- effect nur bei echten Side-Effects
- kein Lifecycle-orchestriertes Setup
users = signal<User[]>([]);
activeUsers = computed(() =>
this.users().filter(u => u.active)
);
Charakteristik
State ist:
- explizit
- synchron ableitbar
- lokal kontrollierbar
Mutation wird ersetzt durch:
Ableitung.
Vergleich – Imperativ vs. Reaktiv
| Imperatives Angular | Reaktives Angular |
|---|---|
| Lifecycle steuert Logik | Datenfluss steuert UI |
| subscribe() überall | async Pipe / Signals |
| Zustand wird gesetzt | Zustand wird abgeleitet |
| Klassenhierarchie | Komposition |
| Mutation | Immutability |
| Seiteneffekte verteilt | Seiteneffekte isoliert |
Typische Migrationspfade
Von 0 → 2
- Services einführen
- Streams statt direkten API-Aufrufen
- takeUntilDestroyed nutzen
Von 2 → 3
- keine Spiegelzustände
- async Pipe konsequent
- ViewModel-Streams einführen
Von 3 → 4
- Signals einsetzen
- computed statt Getter
- effect nur für echte Side-Effects
Reifegrad-Indikatoren
Niedrige Reife
-
3 subscribe() pro Komponente
- Basisklassen für Lifecycle
- lokale Mutable Arrays
- Promise-Konvertierung
Hohe Reife
- keine manuelle Unsubscription
- keine Spiegelzustände
- VM-Pattern
- minimale Lifecycle-Logik
- klare State-Ableitung
Architekturprinzip
Angular-Komponenten sollen State ableiten, nicht orchestrieren.
Kernaussage
Imperatives Angular funktioniert.
Reaktives Angular skaliert.
Je größer das System, desto höher muss der Reifegrad sein, um Wartbarkeit, Testbarkeit und Stabilität zu sichern.
Schlussgedanke
Reifegrad bedeutet nicht:
„mehr RxJS“.
Sondern:
weniger Kontrolle – mehr Ableitung.
Reaktive Architektur reduziert Komplexität, indem sie expliziten Kontrollfluss durch deklarative Struktur ersetzt.