Enterprise Angular Applications with NgRx and Nx
  • Introduction
  • Introduction
    • Introduction
    • Course Description
    • Resources
    • 0 - Environment Setup
    • 1a - Brief Introduction To Angular
    • 1b - Brief Introduction To Angular
    • 1c - Brief Introduction To Angular
    • 2 - Creating an Nx Workspace
    • 3 - Generating components and Nx lib
    • 4 - Add JSON server
    • 5 - Angular Services
    • 6 - Angular Material
    • 7 - Reactive Forms
    • 8 - Layout Lib and BehaviorSubjects
    • 9 - Route Guards and Products Lib
    • 10 - NgRx Introduction
    • 11 - Adding NgRx to Nx App
    • 12 - Strong Typing the State and Actions
    • 13 - NgRx Effects
    • 14 - NgRx Selectors
    • 15 - Add Products NgRx Feature Module
    • 16 - Entity State Adapter
    • 17 - Router Store
    • 18 - Deploying An Nx Monorepo
Powered by GitBook
On this page
  • 1. npm install entity from NgRx
  • 2. Use entity adapter in reducer
  • 3. Update the default reducer logic
  • 4. Add selector methods to bottom of reducer
  • 5. Add default selectors to use entity adapter
  • 6. Examine State tree in Devtools
  1. Introduction

16 - Entity State Adapter

In this section we add entity adapter

Previous15 - Add Products NgRx Feature ModuleNext17 - Router Store

Last updated 3 years ago

1. npm install entity from NgRx

In the past you had to install the @ngrx/entity package separately. Now, you might have noticed in step 13 when we generated our store with the @nrwl/angular:ngrx schematic, it was added automatically. Here is a brief overview of the Entity package and why we use it.

The store is like an in-memory database, and the Entity class gives a unique identifier to each object similar to a primary key. In this way objects can then be flattened out and linked together using the entity unique identifiers, just like in a database. This makes it simpler to combine the multiple entities and 'join' them via a selector query.

The Entity State format consists of the objects in a map called "entities", and store the order of the objects in an array of ids.

The Adapter is a utility class that provides a series of functions designed to make it really simple to manipulate the entity state, such as changing the order.

You can find out more by reading .

2. Use entity adapter in reducer

  • Extend ProductState with EntityState. By default it will make an entities and ids dictionary. You Add to this any state properties you desire.

  • Create an adapter and use it's getInitialState method to make the initial state

libs/products/src/lib/+state/products.reducer.ts
import { createReducer, on, Action } from '@ngrx/store';
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import * as ProductsActions from './products.actions';
import { Product } from '@demo-app/data-models';

export const PRODUCTS_FEATURE_KEY = 'products';

export interface ProductsData extends EntityState<Product> {
  selectedProductId?: string | number;
  loading: boolean;
  error?: string | null;
}

export interface ProductsState {
  readonly products: ProductsData;
}

export const productsAdapter: EntityAdapter<Product> = createEntityAdapter<Product>({});

export interface ProductsState {
  readonly products: ProductsData;
}

export const productsAdapter: EntityAdapter<Product> = createEntityAdapter<Product>({});

export const initialState: ProductsData = productsAdapter.getInitialState({
  error: '',
  selectedProductId: null,
  loading: false,
});
/// Abbreviated

3. Update the default reducer logic

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.

  • addOne: Add one entity to the collection

  • addMany: Add multiple entities to the collection

  • setAll: Replace current collection with provided collection

  • setOne: Add or Replace one entity in the collection.

  • setMany: Add or Replace multiple entities in the collection

  • removeOne: Remove one entity from the collection

  • removeMany: Remove multiple entities from the collection

  • 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

  • mapOne: Update one entity in the collection by defining a map function

  • map: Update multiple entities in the collection by defining a map function, similar to Array.map

libs/products/src/lib/+state/products.reducer.ts
export const productsReducer = createReducer(
  initialState,
  on(ProductsActions.loadProducts, (state) => ({
    ...state,
    loading: false,
  })),
  on(ProductsActions.loadProductsSuccess, (state, { payload: products }) => {
    return productsAdapter.setAll(products, state);
  }),
  on(ProductsActions.loadProductsFailure, (state, { error }) =>
    productsAdapter.removeAll({
      ...state,
      error,
    })
  )
);

export function reducer(state: State | undefined, action: Action) {
  return productsReducer(state, action);
}

4. Add selector methods to bottom of reducer

libs/products/src/lib/+state/products.reducer.ts
export const getSelectedProductId = (state: ProductsData) =>
  state.selectedProductId;

export const {
  // select the array of user ids
  selectIds: selectProductIds,

  // select the dictionary of Products entities
  selectEntities: selectProductEntities,

  // select the array of Products
  selectAll: selectAllProducts,

  // select the total Products count
  selectTotal: selectProductsTotal
} = productsAdapter.getSelectors();

5. Add default selectors to use entity adapter

libs/products/src/lib/+state/products.selectors.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { ProductsData } from './products.reducer';
import * as fromProduct from './products.reducer';

const getProductsState = createFeatureSelector<ProductsData>('products');

const getProducts = createSelector(
  getProductsState,
  fromProduct.selectAllProducts
);
const getProductEntities = createSelector(
  getProductsState,
  fromProduct.selectProductEntities
);
const getSelectedProductId = createSelector(
  getProductsState,
  fromProduct.getSelectedProductId
);
const getSelectedProduct = createSelector(
  getProductEntities,
  getSelectedProductId,
  (productsDictionary, id) => {
    return productsDictionary[id];
  }
);

export const productsQuery = {
  getProducts,
  getProductEntities,
  getSelectedProductId,
  getSelectedProduct,
};

6. Examine State tree in Devtools

At this point, if you run the app however, you might get an error like this:

Error: libs/products/src/lib/containers/products/products.component.ts:21:5 - error TS2322: Type 'Observable<ProductsEntity[]>' is not assignable to type 'Observable<Product[]>'.
  Type 'ProductsEntity[]' is not assignable to type 'Product[]'.
    Type 'ProductsEntity' is missing the following properties from type 'Product': name, category
21     this.products$ = this.store.pipe(select(productsQuery.getProducts));

The app is not going to run now without updating the products component. This will be fixed in the next step.

this Angular University piece on the subject
https://ngrx.io/guide/entity/adapter
Entities and Ids vs Arrays in the State Tree