NgRx Workshop: Part 6 - NgRx Entity

NgRx Workshop: Part 6 - NgRx Entity

  • 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