NgRx #
Präliminarien #
Zustände in einer Webapplikation #
- Server state
- Persistent state
- The URL and router state
- Client state
- Transient client state
- Local UI state
@ngrx/store #
Übersicht #
- Action: Token, mit dem eine Änderung am state initiiert wird
- ActionReducerMap: Register für einzelne reducers. Wird in *feature reducers *(üblicherweise in
index.ts
) erweitert und mit Feature-spezifischen reducers ergänzt. InAppModule
durchStoreModule
konfiguriert. - ActionReducer: Wird benötigt, um reducers wie ein Logger zu erstellen. Wird mit
MetaReducer
konfiguriert. - MetaReducer: Array, welches die action reducers enthält. Wird in
AppModule
definiert und mithilfe vonStoreModule
konfiguriert. - StoreModule: Modul in
@ngrx/store
zur Konfiguration von reducers inAppModule
- createFeatureSelector: Erstellt ein Feature-Selektor für einen state.
- createSelector: Erstellt einen Selektor als Wrapper für einen state.
- Store: Stellt
Store.select()
undStore.dispatch()
zur Verfügung, um mit reducers arbeiten zu können.Store.select()
wählt einen selector aus, währendStore.dispatch()
eine action zum reducer schickt.
Action #
Actions sind Nachrichten, die zur Änderungen des state verschickt werden. Sie haben einen type
und eventuell auch ein payload
-Objekt (im constructor mitgegeben):
export interface Action {
type: string;
}
Typischerweise ist eine actions.ts
-Datei folgendermassen aufgebaut:
// 1. Typen
export enum BookActionTypes {
LoadBook = '[Book Exists Guard] Load Book',
// Weitere Typen
}
// 2. Actions
export class LoadBook implements Action {
readonly type = BookActionTypes.LoadBook;
constructor(public payload: Book) {}
}
// Weitere Actions
// 3. Union Type der Actions
export type BookActionsUnion = LoadBook;
Actions können in folgende drei Kategorien aufgeteilt werden:
- Commands: Vergleichbar mit dem Aufruf einer Methode: Nur ein Handler zur Verarbeitung der Aktion ist vorgesehen und normalerweise wird ein Rückgabewert (Wert oder Fehler) erwartet. Da es sich um eine “Tätigkeit” handelt, beginnt der Name entsprechender Aktionen in der Regel mit einem Verb
- Documents: Benachrichtigung über ein Update einer Entität. Ein spezifischer Handler ist nicht vorgesehen (es können durchaus auch mehrere sein). Eine Antwort wird ebenfalls nicht erwartet. Der Name von Documents ist in der Regel ein Substantiv oder ein substantiviertes Verb, und der payload enthält möglicherweise eine Dokumenten-ID oder einen gleichwertigen Identifier
- Events: Benachrichtigung über einen geschehenen Event. Wie bei Documents ist kein spezifischer Handler vorgesehen, und eine Antwort wird nicht erwartet. Der Name ist eine Beschreibung des Events, und der payload enthält möglicherweise einen Zeitstempel
Für eine einzige Änderung am Zustand werden oftmals verschiedene Actiontypen kombiniert.
ActionReducerMap #
Typ der Map, welche die Reducer-Funktionen eines Moduls enthält. Die entsprechende Instanz wird im Modul mit StoreModule.forRoot
bzw. StoreModule.forFeature
injiziert.
Implementation:
export type ActionReducerMap<T, V extends Action = Action> = {
[p in keyof T]: ActionReducer<T[p], V>
};
Beispiel: export const reducers: ActionReducerMap = { layout: fromLayout.reducer, router: fromRouter.routerReducer, };
ActionReducer #
Interface, welche durch die Reducer-Funktionen implementiert wird
Implementation: export interface ActionReducer<T, V extends Action = Action> { (state: T | undefined, action: V): T; }
Beispiel: export function reducer( state: State = initialState, action: LayoutActions.LayoutActionsUnion ): State { switch (action.type) { case LayoutActions.LayoutActionTypes.CloseSidenav: return { showSidenav: false, };
case LayoutActions.LayoutActionTypes.OpenSidenav:
return {
showSidenav: true,
};
default:
return state;
}
}
MetaReducer #
MetaReducers sind Wrapper um reducers, welche als “hooks” actions vor der Prozessierung durch den inneren reducer abfangen und eigene Prozesse anstossen können.
Implementation: export type MetaReducer<T, V extends Action = Action> = ( reducer: ActionReducer<T, V> ) => ActionReducer<T, V>;
Beispiel: export function debug(reducer: ActionReducer): ActionReducer { return function(state, action) { console.log(‘state’, state); console.log(‘action’, action);
return reducer(state, action);
};
}
StoreModule #
Enthält statische Methoden zur Erstellung von root- bzw. feature-Stores.
forRoot
nimmt *reducers *in Form einer ActionReducerMap
oder einem InjectionToken<ActionReducerMap>
sowie ein optionales Konfigurationsobjekt entgegen:
export type StoreConfig<T, V extends Action = Action> = {
initialState?: InitialState;
reducerFactory?: ActionReducerFactory<T, V>;
metaReducers?: MetaReducer<T, V>[];
};
forFeature
nimmt zusätzlich als erstes Argument einen featureName
entgegen.
createFeatureSelector #
Erstellt einen Selektor des root-Elementes des Feature-Stores. Der Name des Feature-Stores ist sein einziges Argument.
createSelector #
Hilfsfunktion für das Erstellen von Selektoren. Sie sind implementiert für einen bis maximal acht Selektoren sowie jeweils einer Projektorfunktion, welche das gewünschte Feld im Store zurückgibt. Rückgabewert der ganzen Funktion ist jeweils ein memoisierter Selektor.
Store #
Enthält Funktionen, um Aktionen auszulösen und den State mittels Selektoren abzufragen
select
ist eine überladene Funktion zum Abfragen des States. Mögliche Argumentenreihen:
- Selektorfunktion mit optionalen Properties
- Ein Tuple mit Selektorliteralen
Store.dispatch()
nimmt eine Instanz einer Action entgegen
@ngrx/effects #
Effects haben drei Aufgaben:
- Entscheiden, wie actions prozessiert werden sollen (action deciders)
- Filtering deciders: Filtern actions basierend auf dem Typ der action. In NgRx gibt es dafür einen speziellen Operator:
ofType
- Content-based deciders: Entscheiden auf Grund des Inhalts (payloads), welche weitere action(s) eine action auslösen soll
- Context-based deciders: Entscheiden auf Grund von Umgebungsinformationen (bspw. aus Environment-Dateien), welche action(s) eine action auslösen soll
- Splitter: Wandelt eine action in einen Array von actions um. Dies ist nützlich, um eine action besser zu testen oder zu beobachten
- Aggregator: Aggregiert verschiedene actions zu einer einzigen (das Gegenteil vom splitter)
- Filtering deciders: Filtern actions basierend auf dem Typ der action. In NgRx gibt es dafür einen speziellen Operator:
- Transformieren actions in andere actions (action transformers)
- Content enricher: Reichert eine action mit weiteren Informationen in der payload an (bspw. aus dem Backend)
- Normalizer & canonical actions: Übersetzt verschiedene actions zu einer einzigen kanonischen action
- Führen Seiteneffekte aus
Effects ändern aber nie den Zustand der Applikation. Dies ist exklusiv Sache von reducers.
Effects-Klassen wird die Actions
-Klasse injiziert, einem observable mit allen actions, welche in der Applikation angestossen werden.
@ngrx/schematics #
Action #
Erstellt eine neue Action-Datei, welche ein enum mit Action-Typen, eine Beispiel-Action-Klasse und eine exportierte Type-Union von Action-Klassen enthält
ng g a ActionName [options]
Optionen:
--flat
: Action-Datei wird nicht in einem nach der Action benannten Verzeichnis gespeichert (true
)-g
/--group
: Speichert Datei innerhalb einesactions
-Verzeichnis (false
)--spec
: Erstellt zusätzliche spec-Datei (false
)
Container #
Erstellt eine Komponente mit injiziertem Store
ng g co ComponentName [options]
Optionen:
--state
: Pfad zur Datei mit dem State-Interface--stateInterface
: Name eienes Interface zur Typisierung des verwendeten State- … alle Optionen für Komponenten
Effect #
Erstellt ein Effect-Datei
ng g ef EffectName [options]
Optionen:
--flat
: Effect-Datei wird nicht in einem nach dem Effect benannten Verzeichnis gespeichert (true
)-g
/--group
: Speichert Datei innerhalb eineseffects
-Verzeichnis (false
)-m
/--module
: Pfad zum Modul, wo der Effect registriert werden soll--root
: Zusammen mit dem-m
-Flag verwendet, wird ein Effekt im angegeben Modul alsEffectsModule.forRoot
registriert (false
)--spec
: Erstellt zusätzliche spec-Datei (true
)
Entity #
Erstellt ein Set von Entity-Dateien: Vordefinierte Actions, ein Collection-Model und ein Reducer mit State-Selektoren
ng g en EntityName [options]
Optionen:
--flat
: Dateien werden nicht in einem nach der Entity benannten Verzeichnis gespeichert (true
)-m
/--module
: Pfad zum Modul, wo der Reducer registriert werden soll-r
/--reducers
: Pfad zu einer Reducer-Datei, der ein State-Interface sowie Action Reducers enthält. Das Entity-Interface wird dem ersten Interface in der betreffenden Datei hinzugefügt, der Entity-Reducer wird dem ersten Objekt vom TypActionReducerMap
hinzugefügt--spec
: Erstellt zusätzliche spec-Datei (true
)
Feature #
Erstellt ein Feature-Set, welches eine Actions-, eine Effects- und eine Reducer-Datei enthält
ng g f FeatureName [options]
Optionen:
--flat
: Effects-Datei wird nicht in einem nach dem Feature benannten Verzeichnis gespeichert (true
)-g
/--group
: Speichert Dateien innerhalb eigener Verzeichnise (false
)-m
/--module
: Pfad zum Modul, wo der Feature Reducer registriert werden soll-r
/--reducers
: Pfad zu einer Reducer-Datei, der ein State-Interface sowie Action Reducers enthält. Das Feature-Interface wird dem ersten Interface in der betreffenden Datei hinzugefügt, der Feature-Reducer wird dem ersten Objekt vom TypActionReducerMap
hinzugefügt--spec
: Erstellt zusätzliche spec-Datei (true
)
Reducer #
--flat
: Reducer-Datei wird nicht in einem nach dem Feature benannten Verzeichnis gespeichert (true
)-g
/--group
: Speichert Reducer-Datei innerhalb einesreducers
-Verzeichnis (false
)-m
/--module
: Pfad zum Modul, wo der Reducer registriert werden soll-r
/--reducers
: Pfad zu einer Reducer-Datei, der ein State-Interface sowie Action Reducers enthält. Das Reducer-Interface wird dem ersten Interface in der betreffenden Datei hinzugefügt, der Reducer selbst wird dem ersten Objekt vom TypActionReducerMap
hinzugefügt-r
/--reducers
: Pfad zu einer Reducer-Datei, der ein State-Interface sowie Action Reducers enthält. Das Feature-Interface wird dem ersten Interface in der betreffenden Datei hinzugefügt, der Feature-Reducer wird dem ersten Objekt vom TypActionReducerMap
hinzugefügt--spec
: Erstellt zusätzliche spec-Datei (true
)
Store #
Erstellt das initiale Setup für die State-Verwaltung und die Registrierung neuer Feature-States. Integriert zudem @ngrx/store-devtools
und generiert eine Datei, welche eine State-Interface, eine ActionReducerMap und assoziierte Meta-Reducers enthält.
ng g st State [options]
Options:
-m
/--module
: Pfad zum Modul, wo der Feature State registriert werden soll--root
: Zusammen mit dem-m
-Flag verwendet, wird der State im angegeben Modul alsEffectsModule.forRoot
registriert.true
nur für globalen Store verwenden! (false
)--statePath
: Pfad zum Verzeichnis, wo die State-Datei erstellt werden soll (reducers
)--stateInterface
: Name eines Interface zur Typisierung des verwendeten State. Falls die--root
-Option gewählt wird, wird der Name des Stores verwendet.
Beispielapplikation aus dem Platform-Repository #
Struktur eines Feature-Moduls #
actions
: Actions im Sinne von Reduxcomponents
: Presentation components, d.h. Komponenten ohne eigene Logik (ausser@Input()
bzw@Output()
sowie getters zur Verwendung im Template)containers
: Container components, d.h. Komponenten mit eigener Logik (bspw. Organisierung und Orchestrierung der Aktivitäten von Kindkomponenten und Services), die potentiell den Zustand der Applikation verändern. Im Rahmen von NgRx sind dies Komponenten, die actions auslösen und auf Zustandsveränderungen reagieren (mittelsselect
). Im Gegenteil zu presentation components werden sie auch getestet.effects
: Services mit Seiteneffekten im Sinne von Reduxguards
: Route guardspipes
: Pipes (in der Beispielapplikation nur im globalenshared
-Ordner)models
: Models (als interfaces) sowie Funktionen zur Generierung von Mock-Datensätzenreducers
: Reducers im Sinne von Reduxservices
: Services, die zur Kommunikation mit externen Diensten (im Fall der Beispielapplikation mit Google Books) verwendet werdenxyz.module.ts
:@NgModule()
-dekorierte Klasse
Weitere Anmerkungen zur Struktur #
- In fast jedem Verzeichnis werden sog. barrels verwendet. Diese dienen dazu, einen gemeinsamen Import von Klassen in einem Verzeichnis zu ermöglichen.
- Die
app.component
wird demcore
-Modul hinzugefügt und befindet sich auch in dessen Verzeichnis - Im globalen
shared
-Verzeichnis befindet sich neben dempipes
-Verzeichnis eine Dateiutils.ts
, welche Utility-Klassen und -Interfaces enthält