Angular Signals, Funkturm, Angular Logo
Frontend

Signals in Angular – der RxJS Killer?

Das Angular-Team veröffentlichte kürzlich endlich die ersten Kommentare und Prototypen zur Integration von Signals in Angular, einem neuen reaktiven Element, in das Kernframework.

Features von Signals in Angular

  • Signals sind teilweise ebenso leistungsstark wie RxJS, aber mit einer einfacheren Syntax.
  • Wenn sie implementiert werden, könnte dies die Best Practices der Änderungserkennung grundlegend verändern und möglicherweise zone.js überflüssig machen.
  • Signale werden RxJS allerdings nicht so schnell ersetzen.

Was sind Signals?

Ein Signal ist eine Quelle von Werten. Signals sind in letzter Zeit durch Solid.js (ein anderes Javascript Framework) populär geworden, das sie wie folgt beschreibt:

„Signale sind das Herzstück der Reaktivität in Solid. Sie enthalten Werte, die sich im Laufe der Zeit ändern. Wenn Sie den Wert eines Signals ändern, wird automatisch alles aktualisiert, was es verwendet.“

Wenn Sie mit RxJS vertraut sind, erinnert Sie das möglicherweise an BehaviorSubject oder im Allgemeinen an multicastfähige Werte. Im Gegensatz zu BehaviorSubject erfordern Signale jedoch keine Abonnements, um über Änderungen benachrichtigt zu werden.

Mit Signalen werden Abonnements automatisch unter der Oberfläche erstellt und zerstört. Ähnlich wie beim async-Pipe in RxJS. Im Gegensatz zu Observables müssen Signale außerhalb des Templates nicht abonniert werden.

Signale versuchen, einige der Komplexitäten von RxJS zu verbergen.

Wie bereits erwähnt, enthalten Signale Werte, die sich im Laufe der Zeit ändern. Schauen wir uns das genauer an.

Beispiel für Signals in Angular

Das folgende Beispiel basiert auf Introduction/Signals und zeigt, wie sich der Wert von „count“ im Laufe der Zeit ändert und im DOM widergespiegelt wird.

Anschließend werden wir sehen, wie dies in Angular implementiert werden kann.

Beginnen wir mit der Erstellung eines Signals.

  1. Erstellen Sie ein Signal, indem Sie createSignal aus solid-js importieren.
  2. Übergeben Sie ein Argument an createSignal. Dies ist der anfängliche Wert.
  3. createSignal gibt ein Array mit zwei Funktionen zurück: ein Getter und ein Setter.
  4. Durch Destrukturierung können wir diese Funktionen beliebig benennen. In diesem Fall nennen wir den Getter „count“ und den Setter „setCount“.
const [count, setCount] = createSignal(0);

Interessant ist die Ähnlichkeit mit React! Die Syntax erinnert an einen Hook und funktioniert so ziemlich auf die gleiche Weise. Die Ähnlichkeiten enden hier nicht. Der allgemeine Ansatz von Solid besteht darin, reaktive Berechnungen in Funktionen zu verpacken und JSX zu verwenden.

Hier ist, wie es in Solid funktioniert:

import { createSignal } from "solid-js";

function Counter() {
  const [count, setCount] = createSignal(0);
  return <div>Count: {count()}</div>;
}

Wenn Sie mit React vertraut sind, bemerken Sie vielleicht, dass wir count() als Funktion im Template aufrufen. Das ergibt Sinn, da der erste zurückgegebne Wert ein Getter ist (eine Funktion, die den aktuellen Wert zurückgibt) und nicht der Wert selbst. Daher muss er vom JSX-Compiler ausgewertet werden.

Durch Verwendung von setInterval in JavaScript erstellen wir einen zyklisch inkrementierenden Zähler, der im UI angezeigt wird.

import { createSignal } from "solid-js";

function Counter() {
  const [count, setCount] = createSignal(0);
  setInterval(() => setCount(count() + 1), 1000);
  return <div>Count: {count()}</div>;
}

Bis jetzt ist das keine große Sache, aber erweitern wir das Beispiel, um zu sehen, wie alles, was Signale verwendet, automatisch aktualisiert wird.

Fügen wir der Counter-Komponente folgende Zeile hinzu:

const doubledCount = () => 2 * count();

Wir können dann doubledCount im Template wie folgt verwenden:

return <div>Count: {count()}, Double: {doubledCount()}</div>;

Sie werden bemerken, dass doubledCount() automatisch aktualisiert und im DOM reflektiert wird.

Das ist ziemlich cool, weil es die Entwicklererfahrung vereinfacht und den Code reaktiver macht.

Signals in Angular – Beispiel

Schauen wir uns ein Beispiel an, wie es in Angular aussehen könnte.

Beachten Sie, dass Sie für die Verwendung von Signalen in Angular Angular v16.0.0-next.0 verwenden sollten, das gerade veröffentlicht wurde. Es handelt sich dabei immer noch um einen Prototypen, und Sie sollten ihn möglicherweise nicht in der Produktion einsetzen.

Eine weitere Möglichkeit, es auszuprobieren, besteht darin, StackBlitz zu verwenden und den Signal-Ordner zu importieren.

So könnte Signals in Angular aussehen:

...

@Component({
  selector: 'signals-not-noise',
  standalone: true,
  template: `
    <div>Count: {{ count() }}</div>
    <div>Double: {{ doubledCount() }}</div>
  `
})
export class App {
  count = signal(0);
  doubledCount = computed(() => this.count() * 2);

  constructor() {
    setInterval(() => this.count.set(this.count() + 1), 1000);
  }
}

Da doubleCount count verwendet, das ein Signal ist, wird doubleCount jedes Mal aktualisiert, wenn sich count ändert. Mit anderen Worten, doubleCount hängt von count ab oder wird aus count berechnet, und diese Abhängigkeit wird automatisch aktualisiert, wenn sich count ändert.

Dank computed können wir den Wert anderer Signale nehmen und darauf aufbauen. Außerdem wird er zwischengespeichert, sodass die Berechnung nicht bei jedem Zugriff auf den Wert ausgeführt werden muss, sondern nur, wenn sich der Wert ändert.

Dies führt zu einer feineren Reaktivität, die eine bessere Änderungserkennung unterstützen könnte.

Funktionsaufrufe in Angular-Templates

Werden wir wirklich Funktionsaufrufe in Angular-Templates verwenden!?

Ist das nicht schlecht? Kurze Antwort: Es kann schlecht sein. Längere Antwort: Es ist nicht so schlimm, wenn die Auswertung günstig ist und Signale günstig sein werden. Das Vermeiden von Funktionsaufrufen in Templates ist eine allgemeine Regel, die verhindert, dass etwas zu einem Problem wird, anstatt dass es immer ein Problem ist. Wenn die Funktion, die Sie im Template aufrufen, teure Operationen ausführt, können Probleme auftreten. Das Änderungserkennungssystem muss die Funktion bei jeder Änderung erneut auswerten, und teure Operationen können sich ansammeln.

Was ist suboptimal an RxJS?

Mit RxJS müssten wir etwas wie das Folgende tun:

import { Component } from '@angular/core';
import { BehaviorSubject, map, take } from 'rxjs';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
count$ = new BehaviorSubject(0);
doubleCount$ = this.count$.pipe(map((value) => value * 2));

constructor() {
setInterval(() => {
let currentCount = 0;
this.count$.pipe(take(1)).subscribe((x) => (currentCount = x));
this.count$.next(currentCount + 1);
}, 1000);
}
}

Dies impliziert auch die Verwendung des async-Pipe im Template. Einige Auswirkungen sind:

  • Die Verwaltung eines Nullwerts, bis die asynchrone Anfrage abgeschlossen ist und einen anderen Wert zurückgibt.
  • Die Verwendung des async-Pipe mehrmals erzeugt separate Abonnements und separate HTTP-Anfragen. Möglicherweise möchten Sie shareReplay verwenden, um mehrere Nebeneffekte zu vermeiden.

Signals in Angular vs. RxJS

Es scheint, dass Signale RxJS übernehmen und ersetzen werden. Allerdings handhaben Signale „Race conditions“ nicht sehr gut, daher wird RxJS bleiben und asynchrone Operationen unterstützen. Es ist auch außerdem möglich, Signale in Observables und umgekehrt umzuwandeln.

Die Verwendung von Signalen in Angular könnte außerdem das Erlernen von Angular erleichtern.

Neue Änderungserkennung

Dies könnte eine große Veränderung für Angular sein. Dank Signale könnte man sich eine Zukunft ohne zone.js vorstellen.

Signale ermöglichen eine feingranulare Überprüfung von Änderungen anstelle einer Überprüfung des gesamten Komponentenbaums auf Änderungen. Dadurch könnte die Änderungserkennung in Angular verbessert werden.

Derzeit überprüfen verschiedene Frameworks auf unterschiedlichen Ebenen, was sich im DOM-Baum ändert, z.B. auf

  • App-Ebene – Angular
  • Komponenten-Ebene – React
  • Einzelne Elemente – Solid

Es scheint, dass Angular auf eine feinere Granularität abzielt und dabei Signale interoperabel mit zone.js halten möchte.

Laut Alex Rickabaugh (derselbe Entwickler, von dem „The Little-known Story Behind Angular Standalone Components“ stammt und der Technical Lead im Angular Core Team ist) wird durch Signals ngOnChange überflüssig.


Mehr in dieser Kategorie : Frontend