Angular 16 - Signals
von Felix Binder
Signals ist eine neue Funktion des Angular-Frameworks, die eine vereinfachte Form der reaktiven Programmierung bietet. Reaktive Programmierung ist ein Programmierparadigma, das sich auf den Datenfluss und die Verbreitung von Änderungen konzentriert.
Signals sind seit Version 16 des Angular-Frameworks verfügbar.
Das Konzept
Hier sind einige Punkte aufgelistet, die das Konzept von Signals erklären:
Einfachheit und Direktheit
Signals sind einfacher und direkter als andere reaktive Programmierwerkzeuge in Angular, wie z.B. RxJS. Sie bieten eine einfachere Schnittstelle, um auf Ereignisse oder Datenänderungen zu reagieren.
Synchronität
Signals sind besonders nützlich für synchronen Code, d.h. sie eignen sich gut für Situationen, in denen Daten direkt und sequentiell verarbeitet werden, ohne auf andere asynchrone Operationen warten zu müssen.
Interoperabilität
Es gibt Mechanismen, um zwischen RxJS Observables und Angular Signals zu konvertieren, was eine gewisse Flexibilität und Interoperabilität zwischen den beiden ermöglicht.
Eingebettet in Angular Core
Signals sind in Angular Core integriert, was bedeutet, dass sie nativ mit der Angular Plattform interagieren können, ohne dass zusätzliche Bibliotheken oder Abhängigkeiten benötigt werden.
Benachrichtigung von Abonnenten
Wenn sich der Wert eines Signals ändert, werden alle Abonnenten des Signals automatisch benachrichtigt. Dies ermöglicht eine sofortige Reaktion auf die Änderung, was für die Aufrechterhaltung der Reaktionsfähigkeit und Aktualität der Anwendung von entscheidender Bedeutung ist. Die automatische Benachrichtigung stellt sicher, dass alle relevanten Teile der Anwendung immer über den aktuellen Stand der Daten informiert sind und entsprechend reagieren können.
Vereinfachung bestimmter RxJS-Szenarien
Signals können verwendet werden, um bestimmte Szenarien zu vereinfachen und zu entwirren, die zuvor mit RxJS gehandhabt wurden, insbesondere wenn es sich um synchronen Code handelt.
Im Großen und Ganzen sind Angular Signals eine Möglichkeit, die reaktive Programmierung in Angular-Anwendungen einfacher und direkter zu gestalten, insbesondere in synchronen Programmierszenarien. Sie bieten eine weniger komplexe, aber dennoch effiziente Möglichkeit, auf Datenänderungen zu reagieren und mit ihnen umzugehen.
Wie benutzt man Signals?
Die Initialisierung eines Signals ist unkompliziert. Man ruft die Funktion signal() auf und gibt einen Initialwert an:
Angular übernimmt die Verwaltung der Subscriptions, sodass man die "async"-Pipe, die bei RxJS-Observables erforderlich ist, nicht mehr benötigt.
Signals können auch typisiert sein:
Signals modifizieren - Verwendung von set(), update() und mutate()
set - set(value: T): void
Um einfache Datentypen zu aktualisieren, ruft man die ".set()"-Funktion auf und übergibt den neuen Wert.
update - update(updateFn: (value: T) => T): void
Die ".update()"-Funktion wird verwendet, wenn der neue Wert vom vorherigen abhängig ist. Hier muss eine Update-Funktion übergeben werden.
mutate - mutate(mutatorFn: (value: T) => void): void
Wenn das Signal komplexe Datentypen enthält, können diese Objekte direkt mit der ".mutate()"-Funktion modifiziert werden. Dadurch muss das Objekt nicht komplett ersetzt werden.
Effekte und berechnete Signals
Effekte - effect()
Ein Effekt ist eine Operation, die ausgeführt wird, wenn sich der Wert eines oder mehrerer Signals ändert.
Effekte können mit der "effect()"-Funktion erzeugt werden:
Effekte werden immer mindestens einmal ausgeführt. Dabei werden alle Signals innerhalb des Effektes getrackt. Dies hat zur Folge, dass der Effekt erneut ausgeführt wird, sobald sich der Wert eines der Signals ändert.
Berechnete Signals - computed()
Ein berechnetes Signal erhält seinen Wert in Abhängigkeit von anderen Signals.
Diese Signals können mit der "computed()"-Funktion erzeugt werden:
In diesem Ausschnitt ist "doubleCount" ein berechnetes Signal, das von "count" abhängt. Wenn "count" aktualisiert wird, weiß Angular, dass alles, was von "count" oder "doubleCount" abhängt, ebenfalls aktualisiert werden muss.
Signals vs. RxJS
Signals sind kein Ersatz für RxJS und Observables, sondern zielen darauf ab, reaktiven Code - der traditionell von RxJS verwaltet wird - zu vereinfachen und damit die Codebasis übersichtlicher zu gestalten.
Eine parallele Verwendung von Signals und RxJS ist nicht nur möglich, sondern Observables können sogar in Signals umgewandelt werden.
Im Folgenden sind einige Codebeispiele aufgelistet, die die unterschiedlichen Herangehensweisen von Signals und RxJS verdeutlichen:
Laden von Daten in TypeScript
In RxJS ist es erforderlich, das Observable mittels ".subscribe()" zu abonnieren. Ebenso muss diese Subscription beim Zerstören der Komponente wieder aufgehoben werden, um mögliche Performanceprobleme zu vermeiden.
Mit Signals entfällt die Notwendigkeit für ein Subscribe/Unsubscribe – das übernimmt Angular für uns.
Laden von Daten im HTML Template
Bei der Verwendung von RxJS im Template ist kein Subscribe/Unsubscribe notwendig, da die "async"-Pipe diese Aufgabe übernimmt.
Bei Signals muss lediglich der Getter des Signals aufgerufen werden.
Subject vs. Signal
In RxJS können Subjects verwendet werden, um Daten zu speichern. Subjects sind ein spezieller Typ von Observables.
Mit Signals funktioniert dieser Ansatz sehr ähnlich, wobei hier ein Signal anstelle eines Subjects erzeugt wird.
Transformation / Filterung von Daten
In RxJS können Daten mit der ".pipe()"-Funktion und verschiedenen Operatoren transformiert werden.
In Signals gibt es diese Vielzahl an Operatoren nicht. Hier reicht es aus, die "computed()"-Funktion zu verwenden.
So wie "Observable.pipe()" ein neues Observable zurückgibt, liefert "computed()" ein neues Signal zurück.
Fazit
Mit der Einführung von Signals setzt Angular 16 einen neuen Standard für reaktive Programmierlösungen. Es ist eine wichtige Ergänzung, die das Ökosystem sowohl leistungsfähiger als auch intuitiver macht. Ebenso ist die Verwendung von Signals im Vergleich zu RxJS wesentlich einfacher.