Angular Signals ist ein neues Feature in Angular 16, das die Art und Weise, wie Änderungen in Angular-Anwendungen erkannt werden, revolutionieren soll. Seid Angular 17, sind Signals nicht mehr in der DevPreview, sondern fester Bestandteil des Frameworks. Signals bieten eine granularere und effizientere Möglichkeit, Zustandsänderungen zu verfolgen, was zu erheblichen Leistungsverbesserungen führen kann, insbesondere in großen und komplexen Anwendungen.
Was sind Signals?
Ein Signal ist ein Wrapper um einen Wert, der interessierte Benutzer benachrichtigen kann, wenn sich dieser Wert ändert. Signals können beliebige Werte enthalten, von einfachen Primitiven bis hin zu komplexen Datenstrukturen. Signals können schreibbar oder schreibgeschützt sein.
Signals sind Observables (RxJS) ähnlich, aber es gibt einige wichtige Unterschiede. Erstens sind Signals dazu gedacht, Änderungen zu erkennen, während Observables vielseitiger sind. Zweitens sind Signals unveränderlich, d.h. sie können nicht direkt geändert werden. Stattdessen muss ein neues Signal mit dem aktualisierten Wert erzeugt werden. Dadurch werden Signals leichter verständlich und unerwartete Nebeneffekte werden vermieden.
Hier ist ein Beispiel für die Verwendung von Signals in einer Angular-Anwendung:
import { signal, computed } from '@angular/core';
export class AppComponent {
count: WritableSignal<number> = signal(0);
doubleCount: Signal<number> = computed(() => this.count() * 2);
increment() {
this.count.set(this.count() + 1);
}
decrement() {
this.count.set(this.count() - 1);
}
}
In diesem Beispiel werden zwei Signals definiert: count
und doubleCount
. Das Signal count
ist ein schreibbares Signal, d.h. sein Wert kann geändert werden. Das Signal doubleCount
ist ein berechnetes Signal, d. h. sein Wert wird aus dem Signal count
abgeleitet.
Die computed
Funktion erhält als Argument eine Ableitungsfunktion. Die Ableitungsfunktion wird immer dann aufgerufen, wenn sich die Signals, von denen sie abhängt, ändern. In diesem Fall hängt das Signal doubleCount
vom Signal count
ab. Das bedeutet, dass das Signal doubleCount
immer dann aktualisiert wird, wenn sich das Signal count
ändert.
Um die Signals zu verwenden, können wir sie einfach abonnieren. Wir könnten z.B. das Signal doubleCount
abonnieren und das DOM aktualisieren, wenn sich sein Wert ändert:
<p>The double count is {{ doubleCount | async }}</p>
Warum sollte man Signals verwenden?
Die Verwendung von Signals in Angular-Anwendungen hat mehrere Vorteile:
- Performance: Signals können zu erheblichen Performance-Verbesserungen führen, indem sie die Anzahl der notwendigen Änderungserkennungen reduzieren. Dies liegt daran, dass Signals granularer sind als der aktuelle Mechanismus zur Erkennung von Änderungen, der auf Dirty Checking basiert.
- Vereinfachung: Signals sind einfacher zu verwenden und zu verstehen als der derzeitige Mechanismus zur Erkennung von Änderungen. Dies liegt daran, dass Signals unveränderlich sind und eine klare API haben.
- Flexibilität: Signals können zur Implementierung einer Vielzahl unterschiedlicher Reaktionsmuster verwendet werden, z.B. abgeleitete Signals, Memorisierung und Lazy Loading.
Einzigartige Use Cases für Signals
Hier sind einige einzigartige Anwendungsfälle für Signals in Angular-Anwendungen:
- Synchronisierung von Echtzeitdaten: Signals können verwendet werden, um Echtzeitdaten zwischen verschiedenen Komponenten in einer Angular-Anwendung zu synchronisieren. Dies kann bei der Erstellung von Anwendungen wie Chat-Apps und Dashboards nützlich sein.
- Effiziente Animation: Signals können verwendet werden, um Elemente in einer Angular-Anwendung effizient zu animieren. Dies liegt daran, dass Signals verwendet werden können, um Zustandsänderungen zu verfolgen und das DOM nur bei Bedarf zu aktualisieren.
- Lazy Loading: Signals können verwendet werden, um „Lazy Loading“ von Komponenten und Modulen in einer Angular-Anwendung zu implementieren. Dies kann die Performance von Anwendungen verbessern, indem nur die Komponenten und Module geladen werden, die tatsächlich benötigt werden.
Weiterführende Use Cases für Signals
Zusätzlich zu den oben genannten Anwendungsfällen können Signals auch verwendet werden, um fortgeschrittenere Reaktionsmuster zu implementieren, wie z.B:
- Zustandsautomaten: Signals können verwendet werden, um Zustandsautomaten in Angular-Anwendungen zu implementieren. Dies kann nützlich sein, um komplexe Anwendungen mit mehreren Zuständen zu erstellen.
- UI Interaktionen: Signals können verwendet werden, um komplexe UI Interaktionen zu implementieren, z.B. Drag and Drop und Resize.
- Datenvalidierung: Signals können verwendet werden, um Datenvalidierung in Angular-Anwendungen zu implementieren. Dies kann nützlich sein, um sicherzustellen, dass die vom Benutzer eingegebenen Daten gültig sind.
Beispiele für den Einsatz von Signals
Hier sind einige Beispiele für die Verwendung von Signals in Angular-Anwendungen:
Beispiel 1: Synchronisation von Echtzeitdaten
Das folgende Beispiel zeigt, wie Signals verwendet werden, um Echtzeitdaten zwischen zwei Komponenten zu synchronisieren:
import { signal } from '@angular/core';
export class ChatComponent {
messages: Signal<string[]> = signal([]);
sendMessage(message: string) {
this.messages.push(message);
}
}
export class MessageListComponent {
messages: Signal<string[]> = signal([]);
ngOnInit() {
this.messages.subscribe(messages => {
this.messages = messages;
});
}
}
In diesem Beispiel hat die ChatComponent
ein Signal namens messages
, das ein Array mit Nachrichten enthält. Die MessageListComponent
hat ebenfalls ein Signal namens messages
, das ein Array von Nachrichten enthält.
Wenn der Benutzer eine Nachricht an die ChatComponent
sendet, wird das Signal messages
aktualisiert. Die MessageListComponent
ist auf das Signal messages
subscribed, so dass sie aktualisiert wird, wenn sich das Signal messages
ändert. Dadurch wird sichergestellt, dass die MessageListComponent
immer die neuesten Nachrichten anzeigt.
Beispiel 2: Effiziente Animation
import { signal } from '@angular/core';
export class AppComponent {
// Define a signal for the element to be animated.
element: Signal<HTMLElement> = signal(null);
// Subscribe to the signal and update the DOM accordingly.
ngOnInit() {
this.element.subscribe(element => {
// Animate the element.
});
}
// Update the state using a writable signal.
setElement(element: HTMLElement) {
this.element.set(element);
}
}
In diesem Beispiel verwenden wir ein Signal, um das zu animierende Element zu verfolgen. Außerdem abonnieren wir das Signal und aktualisieren das DOM entsprechend, wenn das Signal ausgelöst wird. Schließlich verwenden wir ein beschreibbares Signal, um das zu animierende Element zu aktualisieren.
Um dieses Beispiel zu verwenden, müssen wir zuerst ein Template erstellen, das das zu animierende Element enthält. Zum Beispiel:
<div id="my-element"></div>
Dann müssen wir die AppComponent
in Ihre Komponente injizieren und das Element dem element
Signal zuweisen. Zum Beispiel:
import { Component } from '@angular/core';
import { AppComponent } from './app.component';
@Component({
selector: 'my-component',
templateUrl: './my-component.component.html'
})
export class MyComponent {
constructor(private appComponent: AppComponent) {}
ngOnInit() {
this.appComponent.element.set(document.getElementById('my-element'));
}
}
Zuletzt müssen wir den Code schreiben, um das Element zu animieren. Zum Beispiel:
import { animate, style } from '@angular/animations';
@Component({
selector: 'my-component',
templateUrl: './my-component.component.html',
animations: [
animate('1s', style({
transform: 'translateY(100px)'
}))
]
})
export class MyComponent {
constructor(private appComponent: AppComponent) {}
ngOnInit() {
this.appComponent.element.set(document.getElementById('my-element'));
}
animate() {
// Animate the element.
this.appComponent.element.value.classList.add('animated');
}
}
Wenn wir die Methode animate()
aufrufen, wird das Element animiert, so dass es sich auf der Seite um 100 Pixel nach unten bewegt.
Beispiel 3: Derived Signals
Das folgende Beispiel zeigt, wie man Signals verwendet, um ein abgeleitetes Signal zu implementieren:
import { signal, computed } from '@angular/core';
export class AppComponent {
count: WritableSignal<number> = signal(0);
isEven: Signal<boolean> = computed(() => this.count() % 2 === 0);
increment() {
this.count.set(this.count() + 1);
}
decrement() {
this.count.set(this.count() - 1);
}
}
In diesem Beispiel ist das isEven
Signal ein abgeleitetes Signal, das vom count
Signal abhängt. Jedes Mal, wenn sich das count
Signal ändert, wird das isEven
Signal entsprechend aktualisiert.
Das isEven
Signal kann dann zur bedingten Darstellung von Elementen im DOM verwendet werden. Beispielsweise könnte eine andere Farbe gerendert werden, je nachdem, ob das isEven
Signal true
oder false
ist:
<p class="even" *ngIf="isEven | async">The count is even.</p>
<p class="odd" *ngIf="!isEven | async">The count is odd.</p>
Beispiel 4: Memoization
Das folgende Beispiel zeigt, wie Signals zur Implementierung der Memoisierung verwendet werden können:
import { signal, memoized } from '@angular/core';
export class AppComponent {
expensiveComputation: Signal<number> = memoized(() => {
// Perform an expensive computation here.
return 123;
});
render() {
// Display the result of the expensive computation.
return this.expensiveComputation();
}
}
In diesem Beispiel ist das Signal expensiveComputation
ein memoized Signal. Das bedeutet, dass die Berechnung nur einmal durchgeführt und das Ergebnis zwischengespeichert wird. Spätere Aufrufe des Signals expensiveComputation
geben einfach das zwischengespeicherte Ergebnis zurück.
Dies kann nützlich sein, um die Leistung von Anwendungen zu verbessern, die teure Berechnungen durchführen.
Beispiel 5: Lazy Loading
Das folgende Beispiel zeigt, wie Signals verwendet werden können, um Lazy Loading zu implementieren:
import { signal, lazy } from '@angular/core';
export class AppComponent {
modules: Signal<Array<() => Promise<any>>> = signal([]);
loadModule(moduleName: string) {
const moduleLoader = lazy(() => import(`./modules/${moduleName}.module`));
this.modules.push(moduleLoader);
}
}
In diesem Beispiel enthält das Signal modules
ein Array mit Funktionen zum Laden von Modulen. Wenn der Benutzer auf eine Schaltfläche zum Laden eines Moduls klickt, wird die Methode loadModule()
aufgerufen. Diese Methode fügt dem Signal modules
eine Modulladefunktion hinzu.
Das Signal modules
wird dann subskribiert. Jedes Mal, wenn sich das Signal modules
ändert, werden die Funktionen des Modulladers ausgeführt. Dadurch werden die Module bei Bedarf geladen.
Fazit
Angular Signals ist ein mächtiges neues Feature, das verwendet werden kann, um die Performance, Einfachheit und Flexibilität von Angular-Anwendungen zu verbessern. Obwohl sich Signals noch in der Developer Preview befindet, lohnt es sich, einen Blick darauf zu werfen, wenn Sie nach Möglichkeiten suchen, Ihre Angular-Anwendungen zu verbessern.