Blog
Angular 16 - Signals

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:

// Initialisierung und Verwendung in TypeScript
const count = signal(0);

// Signals sind Getter. Durch Aufrufen von count() erhält man den Wert.
console.log('The count is: ' + count());
<!-- Verwendung im Template -->
<p>{{ count() }}</p>

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:

const count = signal(0);

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.

const name = signal('Foo Bar');

// Zuweisung eines neuen Wertes
name.set('Foo Bar Baz'); 

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.

const count = signal(0);

// Wert des Signals um 1 erhöhen
count.update(value => value + 1);

// Ausgabe: 1
console.log(count());

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.

const messages = signal([{message: 'Hello World!'}]);

// Ändern des Textes der ersten Nachricht im Array, ohne das Array ersetzen zu müssen
messages.mutate(values => values[0].message = 'Hello Signals!');

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:

effect(() => {
  console.log(`The current count is: ${count()}`);
});

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:

const count = signal(0);
const doubleCount = computed(() => count() * 2);

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.

const data: Data;
const dataAsObservable: Observable = of({} as Data);

dataAsObservable.subscribe(d => data = d);

Mit Signals entfällt die Notwendigkeit für ein Subscribe/Unsubscribe – das übernimmt Angular für uns.

const data: Data;
const dataAsSignal: Signal = signal({} as Data);

data = dataAsSignal();

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.

<div>{{ dataAsObservable | async }}</div>

Bei Signals muss lediglich der Getter des Signals aufgerufen werden.

<div>{{ dataAsSignal() }}</div>

Subject vs. Signal

In RxJS können Subjects verwendet werden, um Daten zu speichern. Subjects sind ein spezieller Typ von Observables.

const numberAsSubject = new BehaviorSubject(0);
numberAsSubject.next(1);

Mit Signals funktioniert dieser Ansatz sehr ähnlich, wobei hier ein Signal anstelle eines Subjects erzeugt wird.

const numberAsSignal = signal(0);
numberAsSignal.set(1);

Transformation / Filterung von Daten

In RxJS können Daten mit der ".pipe()"-Funktion und verschiedenen Operatoren transformiert werden.

const dataAsObservable: Observable = of({id: 1});
const id$ = dataAsObservable
  .pipe(
    map(d => d.id)
  );

id$.subscribe(id => console.log(id));

In Signals gibt es diese Vielzahl an Operatoren nicht. Hier reicht es aus, die "computed()"-Funktion zu verwenden.

const dataAsSignal: Signal = signal({id: 1});
const idAsSignal = computed(() => dataAsSignal().id);

console.log(idAsSignal());

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.

From our blog