S

super-dev — full-stack

@super-dev.app · Full-stack · ★ offen für Aufträge
12,8k Abonnenten·47 Videos·Aktiv seit 2017

Full-Stack-Entwickler .NET / Angular mit DevOps-Erfahrung auf Azure Cloud. Auch zu Hause mit Flutter + Firebase. Ich entwickle Produkte, deploye sie und überwache sie.

Zurück zu den Artikeln
ANGULAR
ANGULAR

Anwendungszustand mit NgRx SignalStore

Artikel 4 von 4 — Angular 21 in der Praxis
Zoneless, Signals, resource(), @defer, SignalStore — modernes Frontend, für echte Projekte.

Nicht der gesamte Zustand einer App gehört in ein signal() , das tief unten in einer Komponente vergraben ist. Sobald ein Zustand von mehreren Stellen aus geteilt, abgeleitet und mutiert wird, möchte man eine klare Grenze: schreibgeschützte Selektoren, Methoden, um ihn weiterzuentwickeln. NgRx SignalStore ( @ngrx/signals ) bietet genau das, ohne das Actions/Reducer-Boilerplate des klassischen NgRx.

Anatomie eines signalStore

Ein Store setzt sich aus verketteten Features zusammen. withState deklariert den initialen Zustand, withComputed die abgeleiteten Werte, withMethods die Operationen. Jedes Zustandsfeld wird automatisch zu einem Signal, das auf der Instanz verfügbar ist.

TypeScript
1import { signalStore, withComputed, withMethods, withState } from '@ngrx/signals';
2import { computed } from '@angular/core';
3
4export const CartStore = signalStore(
5 { providedIn: 'root' },
6 withState<{ items: CartItem[] }>({ items: [] }),
7 withComputed(({ items }) => ({
8 total: computed(() => items().reduce((sum, item) => sum + item.price * item.quantity, 0)),
9 count: computed(() => items().length),
10 })),
11 withMethods((store) => ({
12 add(item: CartItem): void {
13 patchState(store, { items: [...store.items(), item] });
14 },
15 clear(): void {
16 patchState(store, { items: [] });
17 },
18 })),
19);

Der Zustand wird niemals direkt mutiert: Man verwendet patchState , das ein unveränderliches Update anwendet und die betroffenen Signals benachrichtigt.

Selektoren als Signals

store.total und store.count sind keine Funktionen, die im Service aufgerufen werden: Sie sind computed , also vollwertige Signals. In einer Komponente liest man sie wie jedes andere Signal, und die zoneless Change Detection rendert nur das neu, was von ihnen abhängt.

TypeScript
1export class CartBadge {
2 private readonly store = inject(CartStore);
3
4 protected readonly count = this.store.count;
5 protected readonly total = this.store.total;
6
7 protected checkout(): void {
8 this.store.clear();
9 }
10}

Komposition mit asynchronen Aufrufen

withMethods kann rxMethod (aus @ngrx/signals/rxjs-interop ) einbinden, um einen RxJS-Stream anzubinden, oder einfach async / await für einen fetch . Die Orchestrierungslogik bleibt im Store, die Komponente bleibt eine View. Hier platziert man auch einen loading -Zustand für ein Stale-While-Revalidate-Pattern.

Store oder einfaches Signal?

Nicht alles braucht einen Store. Ein lokaler Zustand einer Komponente — ein aktiver Tab, das Öffnen eines Menüs — bleibt ein privates signal() : Ein Store würde hier nur unnötige Indirektion hinzufügen. Der SignalStore ist sinnvoll, wenn der Zustand:

  • zwischen mehreren Komponenten oder Routen geteilt wird;
  • von mehreren computed abgeleitet wird, die man zentralisieren möchte;
  • durch Operationen mutiert wird, die man isoliert testen möchte.

Die praktische Regel: Beginne mit lokalen Signals, extrahiere einen Store, wenn du denselben Zustand in eine zweite Komponente kopierst. Die Dokumentation behandelt jedes Feature im SignalStore-Leitfaden .

Der SignalStore ist nicht das NgRx „Actions überall" von gestern. Es ist eine Signal-Fassade: schreibgeschützt als Ausgabe, Methoden als Eingabe, kein Reducer. Man behält die Disziplin eines Stores, ohne dessen Zeremoniell zu bezahlen.
super-dev — portfolio.app
// Weiter in ANGULAR
Eine DOOM-artige Engine im Browser, mit drei Backends
11 min • 1,4k Aufrufe
Eine Angular-App auf zoneless + Signals umstellen
8 min • 4,1k Aufrufe
Daten laden mit resource() und httpResource()
7 min • 3,6k Aufrufe