NgRx Workshop: Part 6 - NgRx Entity

Published on
  • Working with collections should be fast
  • Collections are very common
  • @ngrx/entity provides a common set of basic operations
  • @ngrx/entity provides a common set of basic state derivations

How to add @ngrx/entity

  • Start by creating a state that extends the entityState
// From:

// export interface State {
//     collection: BookModel[];
//     activeBookId: string | null;
// }

// To:
export interface State extends EntityState<BookModel> {
  activeBookId: string | null
}
  • Create an adapter and define the initial state with it.

Note that the collection is no longer needed

// From:

// export const initialState: State = {
//     collection: [],
//     activeBookId: null
// };

// To:
const adapter = createEntityAdapter<BookModel>()

export const initialState: State = adapter.getInitialState({
  activeBookId: null,
})
  • Out of the box, it uses the 'id' as the id, but we can also specify custom id and comparer.
const adapter = createEntityAdapter<BookModel>({
  selectId: (model: BookModel) => model.name,
  sortComparer: (a: BookModel, b: BookModel) => a.name.localeCompare(b.name),
})
  • Refactor the reducers to use the entity adapter
// on(BooksApiActions.bookCreated, (state, action) => {
//     return {
//         collection: createBook(state.collection, action.book),
//         activeBookId: null
//     };
// }),
;(on(BooksApiActions.bookCreated, (state, action) => {
  return adapter.addOne(action.book, {
    ...state,
    activeBookId: null,
  })
}),
  // on(BooksApiActions.bookUpdated, (state, action) => {
  //     return {
  //         collection: updateBook(state.collection, action.book),
  //         activeBookId: null
  //     };
  // }),
  on(BooksApiActions.bookUpdated, (state, action) => {
    return adapter.updateOne(
      { id: action.book.id, changes: action.book },
      {
        ...state,
        activeBookId: null,
      }
    )
  }),
  // on(BooksApiActions.bookDeleted, (state, action) => {
  //     return {
  //         ...state,
  //         collection: deleteBook(state.collection, action.bookId)
  //     };
  // })
  on(BooksApiActions.bookDeleted, (state, action) => {
    return adapter.removeOne(action.bookId, state)
  }))
  • Then create selectors using the entity adapter
// From:
// export const selectAll = (state: State) => state.collection;
// export const selectActiveBookId = (state: State) => state.activeBookId;
// export const selectActiveBook = createSelector(
//     selectAll,
//     selectActiveBookId,
//     (books, activeBookId) => books.find(book => book.id === activeBookId) || null
// );

// To:
export const { selectAll, selectEntities } = adapter.getSelectors()
export const selectActiveBookId = (state: State) => state.activeBookId

Adapter Collection Methods

The entity adapter also provides methods for operations against an entity. These methods can change one or 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.

  • addOne: Add one entity to the collection
  • addMany: Add multiple entities to the collection
  • addAll: Replace current collection with provided collection
  • setOne: Add or Replace one entity in the collection
  • removeOne: Remove one entity from the collection
  • removeMany: Remove multiple entities from the collection, by id or by predicate
  • removeAll: Clear entity collection
  • updateOne: Update one entity in the collection
  • 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
  • map: Update multiple entities in the collection by defining a map function, similar to Array.map