Loader Meta Reducer

Note: Spartacus 2.x is no longer maintained. Please upgrade to the latest version.

Overview

To give better feedback to users, based on their actions, we often have to keep information such as “cart is loading”, “fetching user address failed”, and so on. For every separate application state, we have to keep that meta data beside. Separate for cart, user information, product data and so on. Implementing this logic in all of these places manually would result in having different solutions to the same problem across the codebase. That’s why in spartacus loaderReducer was created. This reducer standardizes meta data handling in the whole state tree. You are able to use it on any depth of the tree, wherever you need it. Apart from the reducer, we also provide utilities for actions and selectors.

Applying the Meta Reducer

Wrapping part of state tree with loader utility is quite simple:

import { loaderReducer } from '@spartacus/core';

export const CART_DATA = '[Cart] Cart Data';

export function getReducers(): ActionReducerMap<CartsState> {
  return {
    active: loaderReducer<CartState>(CART_DATA, cartReducer),
  };
}

To loaderReducer you have to provide unique identifier. In case above it is CART_DATA variable. This identifier will also be used in actions to connect each action to specific state tree.

For keeping simple state (one property), you don’t have to provide reducer. Loader success action will set value with passed payload.

Defining the State Interface

To correctly set type definitions on your state use LoaderState interface.

import { LoaderState } from '@spartacus/core';

export const CART_FEATURE = 'cart';

export interface StateWithCart {
  [CART_FEATURE]: CartsState;
}

export interface CartsState {
  active: LoaderState<CartState>;
}

export interface CartState {
  content: Cart;
  entries: { [code: string]: OrderEntry };
  // ...
}

Creating Actions

To manipulate meta data for loading states loading, success or error, actions created should extend loaders actions. It is very important to extend those actions or make sure to set proper meta on action (preferably using provided meta helpers). In case all of your actions implements standard Action, loader flags won’t work correctly.

Example below will describe it the best:

import { StateLoaderActions } from '@spartacus/core';

// ...

// action's result
// - `loading` flag => true
export class LoadCart extends StateLoaderActions.LoaderLoadAction {
  readonly type = LOAD_CART;
  constructor(public payload: { userId: string; cartId: string }) {
    super(CART_DATA);
  }
}

// action's result
// - `error` flag => (provided error) || true
// - `loading` flag => false
// - `success` flag => false
export class LoadCartFail extends StateLoaderActions.LoaderFailAction {
  readonly type = LOAD_CART_FAIL;
  constructor(public payload: any) {
    super(CART_DATA, payload);
  }
}

// action's result
// - `success` flag => true
// - `loading` flag => false
// - `error` flag => false
export class LoadCartSuccess extends StateLoaderActions.LoaderSuccessAction {
  readonly type = LOAD_CART_SUCCESS;
  constructor(public payload: any) {
    super(CART_DATA);
  }
}

// action's result
// - `loading` flag => false
// - `error` flag => false
// - `success` flag => false
export class ClearCart extends StateLoaderActions.LoaderResetAction {
  readonly type = CLEAR_CART;
  constructor() {
    super(CART_DATA);
  }
}

Working With Selectors

Applying loader reducer changes state shape with additional flags and value which contains wrapped state. Because of that ngrx selectors needs to be adjusted. Spartacus provides utility functions for extracting value key or meta data (loading, error, success).

Available functions:

  • loaderValueSelector
  • loaderLoadingSelector
  • loaderErrorSelector
  • loaderSuccessSelector

Example selectors from cart feature that makes use of those utility functions:

import { StateLoaderSelectors } from '@spartacus/core';

export const getCartsState: MemoizedSelector<
  StateWithCart,
  CartsState
> = createFeatureSelector<CartsState>(CART_FEATURE);

export const getActiveCartState: MemoizedSelector<
  StateWithCart,
  LoaderState<CartState>
> = createSelector(
  getCartsState,
  (cartsState: CartsState) => cartsState.active
);


export const getCartState: MemoizedSelector<
  StateWithCart,
  CartState
> = createSelector(
  getActiveCartState,
  state => StateLoaderSelectors.loaderValueSelector(state)
);

export const getCartLoaded: MemoizedSelector<
  StateWithCart,
  boolean
> = createSelector(
  getActiveCartState,
  state =>
    StateLoaderSelectors.loaderSuccessSelector(state) &&
    !StateLoaderSelectors.loaderLoadingSelector(state) &&
    !StateLoaderSelectors.loaderValueSelector(state).refresh
);