2 Observables - mertenhanisch/ng-architecture GitHub Wiki
Was Observables grundsätzlich sind, setze ich voraus, dafür gibt es genügend Tutorials.
Observables sind in erster Linie Definitionen von "Pipelines" von Transformationen, Filter und sonstiger "Operatoren", wo von irgendwoher Daten kommen, diese durch die Pipeline fließen und dann irgendwo wieder herauskommen. Wichtig ist das Detail, dass es eine "Definition" einer Pipeline ist. Das bedeutet, dass ein deklariertes Observable erst einmal nicht wirklich was tut, sondern immer in irgendeiner Form benutzt bzw. aktiviert werden muss.Diese Aktivierung kann durch eigenen Code angestoßen werden, es gibt aber auch Fälle, wo es hinter der Bühne passiert. Und das ist der Punkt, wo der Unterschied zwischen "Cold" und "Hot" liegt. Kalte Observables muss ich als Programmierer immer selbst anstoßen und unabhängig davon, wie oft und wo ich sie anstoße, fangen sie immer wieder von vorne an.Heiße Observables werden quasi hinter der Bühne aktiviert und wenn man sie in seinem Code anstößt, dann ist es so, als ob man mitten in der Vorstellung ins Kino kommt. Den Anfang des Films hat man verpasst.Cold Observable: Video on Demand, ich kann anfangen, wann ich willHot Observable: Live TV, wenn ich anfange, ist das Geschehen schon in Gang. Später beim "Redux-Store" werden wir es hauptsächlich mit heißen Observable zu tun haben.
Beim Anstoßen eines Observables bekommt man eine Subscription zurück. Diese Subscription ist wie ein Newsletter: solange man nicht aktiv kündigt, also unsubscribed, bleibt das Observable dahinter aktiv und wird immer arbeiten. Das kann sogar so weit führen, dass selbst nach Ende der Lebenszeit einer Angular-Component eine Subscription, die durch diese Component ausgelöst wurde, weiter arbeitet und dadurch Memory Leaks verursacht und CPU-Zeit verbrät. Deshalb ist es sehr wichtig, sich bei jedem Subscribe Gedanken darüber zu machen, wie man diese Subscription wieder los wird.Da es hier um die Nutzung von Observables in Angular-Components geht, ist es am einfachsten, sich den Lebenszyklus einer Component zu Nutze zu machen. Es geht hierbei vor allem um langlebige Observables, die während der ganzen Lebenszeit der Component aktiv sein sollen. Kurze Observable wie z.B. ein HttpClient-Request, sind davon eher unberührt - aber auch da kann man diese Technik nutzen. Was hilft es, wenn der Http-Request irgendwann zurückkommt, die Component aber schon gar nicht mehr da ist, die was mit den Daten anfangen könnte...Angular bietet den OnDestroy-Lifecycle-Hook. Es gibt verschiedene Formen, diesen für das Unsubscribe eines Observables zu nutzen. Der einfachste und flexibelste Weg ist folgender (warum der in dieser Architektur der sinnvollste Weg ist, wird sich später noch ergeben).
Info: Codebeispiele sind hier nur aus dem Stehgreif getippt und ungetestet und sind nicht unbedingt lauffähig.
@Component({...})export class SomeComponent implements OnInit, OnDestroy {
private readonly _onDestroy = new Subject<true>();
ngOnDestroy(): void {
this._onDestroy.next(true);
this._onDestroy.complete();
}
ngOnInit(): void {
interval(1_000)
.pipe(takeUntil(this._onDestroy))
.subscribe({
next: (t) => { console.log(t); },
error: (err: any) => { console.error("interval, unexpected error:", err); },
});
}
}
Den OnDestroy-Hook gibt es übrigens nicht nur in Components, sondern auch in Services... Ein weiterer wichtiger Aspekt des Observable-Lebenszyklus ist, dass das Ding nichts mehr tut, sobald im Observable ein Fehler auftritt. Nachdem der Error-Handler aufgerufen wird, ist es vorbei. Deshalb sollte man da mindestens den Fehler in die Console loggen, damit man später beim Debuggen oder Fehleranalyse beim Kunden eine Idee hat, wo etwas schief gegangen ist. Besser sollte man natürlich Fehler, die bei inneren Observables auftreten (kommen wir später noch zu), immer abgefangen und behandelt werden sollten. Das gilt in erster Linie für I/O-Aufrufe wie HttpClient usw., Beispiele folgen dann bei den "best practices" bzw. "design pattern" (keine Ahnung, wie das Kapitel heißen wird...).
Haupteigenschaft, die man über takeUntil wissen sollte: je später er im Observable eingesetzt wird, desto besser ist es. Es gibt zu beachtende Stolpersteine im Zusammenhang mit z.B. shareReplay usw., Artikel dazu findet man im Internet.
https://ncjamieson.com/avoiding-takeuntil-leaks/
https://cartant.medium.com/rxjs-whats-changed-with-sharereplay-65c098843e95
Wie später zu sehen sein wird, ist eine nicht unerhebliche Anzahl langlebiger Observable-Subscriptions ein Kernbestandteil dieser Architektur. Für immer wieder auftretende Standardabläufe wird es noch genügend Beispiele geben, wie diese stabil umgesetzt werden können.