Related Github Documents
Document Properties
KbidD29455
Last Modified15-Jun-2020
Added to KB18-Jun-2020
Public AccessEveryone
StatusOnline
Doc TypeGuidelines, Concepts & Cookbooks
ProductIntershop Progressive Web App

Concept - Intershop Progressive Web App - State Management

State Management

This concept describes how NgRx is integrated into the Intershop Progressive Web App for the application wide state management.

Architecture

State Management

NgRx is a framework for handling state information in Angular applications following the Redux pattern.
It consist of a few basic parts:

State

The state is seen as the single source of truth for getting information of the current application state.
There is only one immutable state per application, which is composed of substates.
To get information out of the state, selectors have to be used.
Changing the state can only be done by dispatching actions.

Selectors

Selectors are functions used to retrieve information about the current state from the store.
The selectors are grouped in a separate file.
They always start the query from the root of the state tree and navigate to the required information.
Selectors return observables which can be held in containers and be bound to in templates.

Actions

Actions are simple objects used to alter the current state via reducers or trigger effects.
They contain the type of the action and an optional payload.
Action objects are not created directly but rather through action creator functions.
Action creators are held in a separate file.
To alter the state synchronously, reducers have to be composed.
To alter the state asynchronously, effects are used.

Reducers

Reducers are pure functions which alter the state synchronously.
They take the previous state and an incoming action to compose a new state.
This state is then published and all listening components react automatically to the new state information.
Reducers should be simple operations which are easily testable.

Effects

Effects use incoming actions to trigger asynchronous tasks like querying REST resources.
After successful or erroneous completion, an effect might trigger another action as a way to alter the current state of the application.

Facades

Facades are injectable instances which provide simplified access to the store via exposed observables and action dispatcher methods.
They should be used in Angular components but not within NgRx artifacts themselves.

File Structure

The file structure looks like this:

src/app/core
        ├─ facades
        |  └─ foobar.facade.ts
        └─ store
           └─ foobar
              ├─ foo
              |  ├─ foo.actions.ts
              |  ├─ foo.effects.ts
              |  ├─ foo.reducer.ts
              |  ├─ foo.selectors.ts
              |  └─ index.ts
              ├─ bar
              |  └─ ...
              ├─ foobar-store.ts
              └─ foobar-store.module.ts

An application module named foobar with substates named foo and bar serves as an example.
The files handling NgRx store should then be contained in the folder foobar.
Each substate should aggregate its store components in separate subfolders correspondingly named foo and bar:

  • foo.actions.ts: This file contains all action creators for the foo state.

  • foo.effects.ts: This file defines an effect class with all its containing effect implementations for the FooState.

  • foo.reducer.ts: This file exports a reducer function which modifies the state of foo. Additionally, the FooState and its initialState is contained here.

  • foo.selectors.ts: This file exports all selectors working on the state of foo.

  • index.ts: This file exports the public API for the state of the foo substate. This includes all specific selectors and actions.

Furthermore, the state of foobar is aggregated in two files:

  • foobar-store.ts: Contains the FoobarState as an aggregate of the foo and bar states.

  • foobar-store.module.ts: Contains aggregations for foobarReducers and foobarEffects of the corresponding substates as well as the store module.

Access to the state slice of foobar is provided with the FoobarFacade located in foobar.facade.ts

Core Store Structure

The PWA has a core state initializing the StoreModule.forRoot and multiple feature modules using StoreModule.forFeature.

  • core: PWA runtime independent of the ICM like configuration, global error handling or @ngrx/router-store integration.
  • shopping: Logic and data for browsing the PWA independent of the current user.
  • customer: Everything user-related (anonymous or logged in) like the current basket or the user profile.
  • content: Everything related to the CMS.
  • general: Minor features that don't require a fully fledged feature store.
  • ...

All store modules are aggregated and imported in the StateManagementModule.

Naming

Related to the example in the previous paragraph, we want to establish a particular naming scheme.

Actions - Types

The string value of the type should contain the feature in brackets and a readable action description.
The description should give hints about the dispatcher of the said action, i.e., actions dispatched due to a HTTP service response should have 'API' in their name, actions dispatched by other actions should have 'Internal' in their description.

  '[Foo Internal] Load Foo',
  '[Foo] Insert Foo',
  '[Foo API] Load Foo Success',
  ...

Actions - Creators

The action creator is a function with a type argument and an optional payload argument.
Its camelCase name should correspond to its type.
The name should not contain 'Action' as the action is always dispatched via the store and is therefore implicitly correctly named.
The action creator is defined using the createAction function.
To attach data to an action, use the payload or httpError adapters from ngrx-creators.ts.

export const loadFoo = createAction('[Foo Internal] Load Foo');
export const loadFooSuccess = createAction('[Foo API] Load Foo Success', payload<{ foo: Foo[] }>());
export const loadFooFail = createAction('[Foo API] Load Foo Fail', httpError());

Reducer

The exported function for the reducer should be named like the substate + 'Reducer' in camelCase.
The reducer function is defined using the createReducer function.
Associations between actions and state changes are defined via callbacks in the on function.
To easily set loading or error states, use the setLoadingOn and setErrorOn helpers from ngrx-creators.ts.

export const fooReducer = createReducer(
  initialState,
  setLoadingOn(loadFoo),
  setErrorOn(loadFooFail),
  on(loadFooSuccess, (state: FooState, action) => {
    // state changes
  }),
  ...
)

State

State interfaces should have the state name followed by 'State' in PascalCase.

export interface FooState {
...

Selectors

Selectors should always be camelCase and start with 'get' or 'is'.

export const getSelectedFoo = createSelector( ...

Facades - Streams

Any field ending with $ indicates that a stream is supplied. i.e. foos$(), bars$, foo$(id).
The facade takes care that the stream will be loaded or initialized.
The naming should just refer to the object itself without any verbs.

Facades - Action Dispatchers

Action dispatcher helpers are represented by methods with verbs. i.e. addFoo(foo), deleteBar(id), clearFoos().

Entity State Adapter for Managing Record Collections: @ngrx/entity

@ngrx/entity provides an API to manipulate and query entity collections.

  • Reduces boilerplate for creating reducers that manage a collection of models.
  • Provides performant CRUD operations for managing entity collections.
  • Extensible type-safe adapters for selecting entity information.

Normalized State

It is important to have a normalized state when working with NgRx.
To give an example, only the product's state should save products.
Every other slice of the state that also uses products must only save identifiers (in this case SKUs) for products.
In selectors, the data can be linked to views to be easily usable by components.

see: NgRx: Normalizing state

Disclaimer

The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.

Customer Support
Knowledge Base
Product Resources
Support Tickets