Profile

NgRx Architecture


Overview

The following diagram represents the overall general flow of application state

Component

The component retrieve the data from Store with Selectors. If the data structure is quite complex, we can add an extra mapper layer to transform store data into a view model. This is very helpful to decouple the view model from the component. For example, I like to create a view model for a complex form group. Each input field has its own form property model including value, visible, validator etc. When a input field’s behavior is not expected, I can easily identify which property of the input field causes the issues.

export class Component extends AbstractConnectableComponent implements OnInit {
    constructor(
        private readonly store: Store,
    ) {
        super(cdr);
    }

    public ngOnInit(): void {
        this.connect<LoanChangesComponent>({
            applicationLoanRenewalOptions: this.store.select(ApplicationSelectors.renewalOptions),
            selectedLoanAccount: this.store.select(LoanAccountsSelectors.selectedLoanAccount),
        });
    }
}

Actions

Actions are one of the important building blocks in NgRx. It’s like a messengers carrying information being sent from one place to another. An action is essentially a plain object. You use ofType operators to capture actions in NgRx. As such, it could be received by components, effects or reducers.

Define a new action

const fetchFee = createAction(
  '[fee][fetch]',
  props<{ feeId: string }>(),
);

Store(State)

export interface FeesState extends Synchronizable, EntityState<Fee> {}

Selectors

Selectors are pure functions used for obtaining slices of store state. The selectors take advantage of memoization for performance improvement. Selectors provide many features when selecting slices of state:

  • Portability
  • Memoization
  • Composition
  • Testability
  • Type Safety
export const feesFeatureKey = 'fees';

const feesFeature = createFeatureSelector<FeesState>(feesFeatureKey);

const isSyncing = createSelector(feesFeature, feesState => feesState.isSyncing);

const fees = createSelector(feesFeature, feesState => values(feesState.entities));

Effects

Capture action in effect and send request via service

export class FeeEffects {
    public fetch$ = createEffect(() => this.actions$.pipe(
      ofType(FeesActions.fetchFee),
      withLatestFrom(this.store.select(ApplicationSelectors.applicationId)),
      mergeMap(([{ feeId }, applicationId]) => this.getFee$(applicationId, feeId).pipe(
        map(fee => FeesActions.fetchFeeSuccess({ fee })),
        catchError(() => of(FeesActions.fetchFeeError())),
      )),
  ));
}

Reducers

Reducers in NgRx are responsible for handling transitions from one state to the next state in your application. Reducer functions handle these transitions by determining which actions to handle based on the action’s type.

export const feesAdapter: EntityAdapter<Fee> = createEntityAdapter<Fee>();
export const feesInitialState: FeesState = feesAdapter.getInitialState({
  isSyncing: false,
});

export const feesReducer = createReducer(
  on(
    FeesActions.fetchFeeSuccess,
    (state, { fee }) => {
      state = feesAdapter.upsertOne(fee, state);
      state.isSyncing = false;
      return state;
    }),
);