GEDOPLAN
Angular

Angular und NgRx SignalStore (2)

Angular
signal store png 2 png

Custom Feature

Oftmals haben wir wiederkehrende Strukturen oder Funktionen, die in mehreren Stores verwendet werden sollen. Für diese Zwecke bietete RxJs die Möglichkeit ein so genanntes Custom Feature zu erstellen, das Stores dann nutzen können, um diese Funktionen und Attribute zu inkludieren. Hier ein Beispiel, wie sich auf einfache Art und Weise ein Event-System in Stores inkludieren lässt:

In einer separaten Datei (z.B. store.utils.ts) definieren wir die Datenstrukturen, die wir verwenden werden. In unserem Beispiel ein Event Objekt mit einem Namen und optionalen zusätzlichen Daten:

interface EventState<T, E> {
  event: { name: T, data?: E } | null,
}

export enum CrudStoreEvent {
  UPDATE = 'UPDATE',
  DELETE = 'DELETE',
  CREATE = 'CREATE'
}

NgRx bietet uns nun eine entsprechende Methode signalStoreFeature, die es uns erlaubt Methoden und Attribute zu definieren, genau wie in unseren Stores auch. In unserem Beispiel definieren wir eine generische Methode, die zum einen den EventTyp definiert um ggf. auch fachlich konkrete Events definieren zu können und ein optionaler Typ für die Definition der Nutzlast. Neben dem Event selber fügen wir noch eine eigene Patch-Methode hinzu, um die Verwendung in den Stores zu vereinfachen:

export function withCrudEvent<T = CrudStoreEvent, E = unknown>() {
  return signalStoreFeature(
    withState<EventState<T, E>>({event: null}),
    withMethods(store => ({
      patchEvent: (event: T, data?: E) => patchState(store, {event: {name: event, data: data}}),
    }))
  );
}

Innerhalb konkreter Stores ist die Verwendung dann ganz einfach: Extension registrieren

export const CocktailStore = signalStore(
  withState(initialState),
  withCrudEvent(),    // << Extension registrieren
  withMethods((
      store,
      cocktailService = inject(CocktailResourceService)
    ) => ({
      createCocktail: rxMethod<Cocktail>(
        pipe(
          switchMap((cocktail) => cocktailService.apiPublicCocktailsPost(cocktail).pipe(
            tapResponse({
              next: () => store.patchEvent(CrudStoreEvent.CREATE),   // << nutzen
              error: (error) => {
                console.log(error)
              },
            })
          )),
        )
      ),

Oder innerhalb der Komponenten

  constructor() {
    effect(() => {
      const event = this.cocktailStore.event();
      if (event?.name === CrudStoreEvent.CREATE) {
       ...
      } 
  }

Entity

Entity Management ist eine smarte Lösung, um im lokalen State, Collections von Objekten über ihre Id zu verwalten, um z.B. in komplexen Formularen Zuordnungen und Listen zu verwalten. Dazu muss lediglich das Feature aktiviert werden:

export const CocktailStore = signalStore(
  withEntities<Cocktail>()
  ...
)

Standardmäßig muss hier ein „Id“ Attribut im Entity-Type vorliegen (number | string), alternativ lässt sich aber auch eine angepasste Id definieren. Das Feature stellt uns nun diverse Methoden und Attribute zur Verfügung, um unsere Entities zu verwalten:

// anlegen
patchState(store, addEntities<Cocktail>(
[
  {id: 1, name: 'Mojito'},
  {id: 2, name: 'Whiskey'}
]
));

// update
patchState(store, updateEntity<Cocktail>(({id: 2, changes: {name: 'Whiskey Updated'}})));

// auslesen
const cocktails: Cocktail[] = store.entities();
const cocktail: Cocktail = store.entityMap()[2];

Testing

Der vollständigkeitshalber ein kurzer Blick aufs Testing. Das ist dank der Kapselung unseres States denkbar einfach:

describe('CocktailStore', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        // standards
        provideZonelessChangeDetection(),
        provideHttpClient(),
        provideHttpClientTesting(),
        // unsere services mocken die normalerweise unsere Rest-Aufrufe durchführen
        provideCocktailResourceServiceMockProvider(),
        // Store providen
        CocktailStore
      ]
    });
  });

  // store Methoden aufrufen und prüfen
  describe("Store-Test", () => {
    it("should load cocktails", () => {
      const store = TestBed.inject(CocktailStore)
      store.loadCocktails();
      expect(store.cocktails().length).toEqual(1);
    })
  })
});

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Bitte füllen Sie dieses Feld aus.
Bitte füllen Sie dieses Feld aus.
Bitte gib eine gültige E-Mail-Adresse ein.
Sie müssen den Bedingungen zustimmen, um fortzufahren.

Autor

Diesen Artikel teilen

LinkedIn
Xing

Gibt es noch Fragen?

Fragen beantworten wir sehr gerne! Schreibe uns einfach per Kontaktformular.

Kurse

weitere Blogbeiträge

quarkus logo horizontal default
Quarkus

Quarkus-Tests mit Logging

Wenn man Tests für Quarkus-Anwendungen schreibt, bedient man sich in der Regel des Unit Test Frameworks JUnit und der Extension…
i18n 2
Webprogrammierung

Angular, i18n mit ngx-translate

Internationalisierung. Eine typische Aufgaben bei der Implementierung von Web-Anwendungen. Diese Anforderung macht auch vor Angular nicht halt. Hier bieten sich…

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!