Signals in Angular vastehn
Angulars Change Detection hot historisch so funktioniert, dass da ganze Komponentenbaum auf Änderungen gprüft wird, sobois irgendwos passiert sei kunnt - a Event feuert, a Timer lauft ab, a HTTP-Antwort kummt zruck. Signals ersetzn des breite, zonenbasierte Durchkämmen durch wos viel Präziseres: reaktive Werte, de genau wissn, wöche Teile von an Template von ihnen abhängn, sodass nur de Teile aktualisiert wern miassn.
Des Problem, des Signals lösn
Klassisches Angular verlasst si auf Zone.js, um asynchrone APIs (setTimeout, addEventListener, fetch und so weiter) zu patchn, damit's woaß, wann de Change Detection laufn soi. Aber es woaß ned, wos sich tatsächlich gändert hot - drum is de Default-Strategie, jede Komponente von da Wurzel obwärts nochamoi zu prüfn und olte und neiche Werte per Referenz zu vergleichn. OnPush engt des ein, indem's Komponenten überspringt, deren @Input()-Referenzen si ned gändert hom, oba des muasst explizit aktiviern, und du muasst imma sicherstöin, dass du neiche Objekt-/Array-Referenzen erzeugst, wenn si Daten ändern.
// Without OnPush: this component gets re-checked on every zone event,
// anywhere in the app, whether count actually changed or not
@Component({
selector: 'app-counter',
template: `<p>Count: {{ count }}</p>`,
})
export class CounterComponent {
count = 0;
}
A Signal is a reaktiver Behälter
import { signal } from '@angular/core';
const count = signal(0);
console.log(count()); // 0 — signals are read by calling them as functions
count.set(1);
count.update((n) => n + 1);
console.log(count()); // 2
Des Wichtige is ned de Getter/Setter-Syntax - sondern, dass's Lesn von an Signal in an reaktiven Kontext (Template, computed(), effect()) den Kontext automatisch ois Abhängigen registriert. Wenn si des Signal ändert, woaß Angular genau, wöche Templates und Berechnungen davon abhängn, ohne dass Zone.js durchs Nochprüfn von ollem raten muass.
@Component({
selector: 'app-counter',
template: `
<p>Count: {{ count() }}</p>
<button (click)="increment()">+1</button>
`,
})
export class CounterComponent {
count = signal(0);
increment() {
this.count.update((n) => n + 1);
}
}
Abgeleiteter State mit computed()
computed() erstöt a Signal, dessen Wert aus ondere Signals abgleitet is, und des si automatisch - und nur dann - neu berechnet, wenn si a Abhängigkeit tatsächlich gändert hot.
const price = signal(29.99);
const quantity = signal(3);
const total = computed(() => price() * quantity());
console.log(total()); // 89.97
quantity.set(5);
console.log(total()); // 149.95
Entscheidend: computed() is lazy und memoized. Es berechnet si ned bei jedem Lesn neu, sondern nur, wenn si seit dem letztn Lesn a Abhängigkeit gändert hot. total() zwoamoi hintereinander zum lesn, ohne zwischendurch price oda quantity zum ändern, gibt beide Moi den gecachten Wert zruck.
Seiteneffekte mit effect()
Manchmoi muasst wos tun, wenn si a Signal ändert - loggn, in localStorage synchronisiern, a Animation auslösn - statt davon an neichn Wert obzuleiten. Dafür is effect() da:
import { effect, signal } from '@angular/core';
const theme = signal<'light' | 'dark'>('light');
effect(() => {
document.body.className = theme(); // reruns whenever theme() changes
localStorage.setItem('theme', theme());
});
effect() lauft einmal sofort, um seine Abhängigkeiten festzulegn, und dann jedes Moi wieda, wenn si irgendein Signal ändert, des während dem Durchlauf glesn wurde. Wia bei computed() is des Dependency-Tracking automatisch - du deklarierst nie a Dependency-Array; des Framework findet's aus den Signals raus, de du im Funktionskörper tatsächlich aufgrufn hast.
Signals und OnPush arbeiten automatisch zamm
A Komponente, de Signals im Template valwendet, kriagt feingranulare Updates, ohne dass ma manuell OnPush aktivieren und immutable Referenzen händisch valwoitn muass - Angular kann a Komponente komplett überspringa, wenn si koa Signal gändert hot, des sie liest, weil's genau woaß, von wöchan Signals jeder Template-Ausdruck abhängt.
@Component({
selector: 'app-cart-total',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<p>Total: {{ total() | currency }}</p>`,
})
export class CartTotalComponent {
price = input.required<number>();
quantity = input.required<number>();
total = computed(() => this.price() * this.quantity());
}
input() (Angulars signalbasierte Input-API) macht @Input()-Bindings söba zu Signals, sodass a Update vom Parent automatisch durch computed() ins Template fließt - mit dem söbn feinen Tracking, ohne manuellen ngOnChanges-Vergleich.
Wos Signals ned ersetzn
Signals modelliern synchronen, reaktiven State - sie sand koa Ersatz für RxJS-Observables, de für asynchrone Event-Streams gboit sand (HTTP-Antworten, WebSocket-Nachrichten, Eingabe-Streams, de ma debouncn/switchMapn wü). Angular bietet toSignal() und toObservable() genau deswegn, weil beide Modelle für verschiedene Dinge nützlich sand: Nimm an Observable, wennst asynchrone Event-Streams zammbaust, und a Signal, wennst an aktuellen Wert darstöst, auf den a Template reagiern soi.
Fazit
Zone.js-basierte Change Detection frogt "is irgendwos passiert?" und prüft breit nach, um's rauszufinden. Signals drehn des um: Jedes Signal woaß genau, wöche Berechnungen und Templates von ihm abhängn, drum sand Updates gezielt statt durchkämmt. Genau de Präzision mocht a Angulars komplett zonenlosen Modus möglich - sobois Change Detection von Signals gsteuert wird, de dem Framework genau sogn, wos si gändert hot, bleibt für Zone.js nix mehr zum Patchn übrig.