22. Use Entity Adapter

In this section, we will normalise our state into dictionaries to make it easier to manage, query and project our state. This is one of the most important steps to make better redux.

1. npm install @ngrx/entity

Entity State adapter for managing record collections. @ngrx/entity provides an API to manipulate and query entity collections.

  1. Reduces boilerplate for creating reducers that manage a collection of models.

  2. Provides performant CRUD operations for managing entity collections.

  3. Extensible type-safe adapters for selecting entity information.

  • npm install the library

npm i @ngrx/entity

2. Make an entity adapter

  • make an adapter and use it to make the initial state and manage collections.

src/app/event/state/attendees/attendees.reducer.ts
<---------- ABBREVIATED CODE SNIPPET ---------->

import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

import { AttendeesActions, AttendeesActionTypes } from './attendees.actions';
import { Attendee } from '../../../models';

export interface State extends EntityState<Attendee> {
  loading: boolean;
  error: any;
}

const adapter: EntityAdapter<Attendee> = createEntityAdapter<Attendee>();

export const intitalState: State = adapter.getInitialState({
  loading: false,
  error: null
});

export function reducer(state = intitalState, action: AttendeesActions): State {

<---------- ABBREVIATED CODE SNIPPET ---------->

3. Update reducer to use entity adapter collection methods

The entity adapter will allow us to take a collection and manage it with a set of Adapter Collection Methods. The entity adapter also provides methods for operations against an entity. These methods can change one to many records at a time. Each method returns the newly modified state if changes were made and the same state if no changes were made. You can read more here https://github.com/ngrx/platform/blob/master/docs/entity/adapter.md.

Method

Action

addOne

Add one entity to the collection

addMany

Add multiple entities to the collection

addAll

Replace current collection with provided collection

removeOne

Remove one entity from the collection

removeAll

Clear entity collection

updateOne

Update one entity in the collectionaddOne

updateMany

Update multiple entities in the collection

upsertOne

Add or Update one entity in the collection

upsertMany

Add or Update multiple entities in the collection

  • Change out manual collection management for adapter collection methods.

  • Make selectors from the adapter with adapter.getSelectors method.

src/app/event/state/attendees/attendees.reducer.ts
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

import { AttendeesActions, AttendeesActionTypes } from './attendees.actions';
import { Attendee } from '../../../models';

export interface State extends EntityState<Attendee> {
  loading: boolean;
  error: any;
}

const adapter: EntityAdapter<Attendee> = createEntityAdapter<Attendee>();

export const intitalState: State = adapter.getInitialState({
  loading: false,
  error: null
});

export function reducer(state = intitalState, action: AttendeesActions): State {
  switch (action.type) {
    case AttendeesActionTypes.LoadAttendees: {
      return adapter.removeAll({
        ...state,
        loading: false,
        error: null
      });
    }

    case AttendeesActionTypes.LoadAttendeesSuccess: {
      return adapter.addAll(action.payload, {
        ...state,
        loading: false,
        error: null
      });
    }

    case AttendeesActionTypes.LoadAttendeesFail: {
      return adapter.removeAll({
        ...state,
        loading: false,
        error: action.payload
      });
    }

    default: {
      return state;
    }
  }
}

export const {
  selectIds,
  selectEntities,
  selectAll,
  selectTotal
} = adapter.getSelectors();

3. Update selectors

  • Update attendee selectors to use the new adapter getSelector methods.

src/app/event/state/attendees/attendees.selectors.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import * as fromAttendee from './../attendees/attendees.reducer';
import { EventState } from '..';

export const getEventState = createFeatureSelector<EventState>('event');
export const getAttendeeState = createSelector(
  getEventState,
  state => state.attendees
);

export const getAttendees = createSelector(
  getAttendeeState,
  fromAttendee.selectAll
);

Last updated