NgRx Course – NgRx Entity

In the last video, we created an effect in our application and added some selectors. In this video, we’re going to explore the ngrx/entity library.

The entity library helps us manage collections of objects. It also reduces the amount of code we need for CRUD operations. It adds ids and entities to the state which has performance implications. So when we need to access an item in the collection we can just refer to its id. Instead of having to loop through the entire collection. In a small application like the one we’re building this is not important. But in real scalable applications, with a lot of data, it’s important to consider the impact on performance.

So instead of having a collection like the one we have in the store right now:

customers: {
  customers: [
    {
      name: 'John Doe',
      phone: '910928392098',
      address: '123 Sun Street',
      membership: 'Platinum',
      id: 1
    },
    {
      name: 'Mary Johnson',
      phone: '808937482734',
      address: '893 Main Voulevard',
      membership: 'Pro',
      id: 2
    }
  ]
}

With the entity library we’ll have ids and entities:

customers: {
    ids: [
      1,
      2
    ],
    entities: {
      '1': {
        name: 'John Doe',
        phone: '910928392098',
        address: '123 Sun Street',
        membership: 'Platinum',
        id: 1
      },
      '2': {
        name: 'Mary Johnson',
        phone: '808937482734',
        address: '893 Main Voulevard',
        membership: 'Pro',
        id: 2
     }
   }
 }

And thanks to the entity library we can manage the collection and perform CRUD operations conveniently. This saves time writing unnecessary code. So, let’s install the Entity library.

We already installed the entity package in a previous video. But in case you didn’t you can install it using the command:

$ npm install @ngrx/entity

And then all the work will be done in our customer.reducer.ts file. First, we need to import the functions from the entity library at the beginning of the file:

// customers/customer.reducer.ts
...
import { EntityState, EntityAdapter, createEntityAdapter } from "@ngrx/entity";
...
// (to be continued)

We then need to make some changes in our CustomerState interface. Create the customerAdapter and modify the initialState:

// customers/customer.reducer.ts (continued)
export interface CustomerState extends EntityState<Customer> {
  selectedCustomerId: number | null;
  loading: boolean;
  loaded: boolean;
  error: string;
}

export interface AppState extends fromRoot.AppState {
  customers: CustomerState;
}

export const customerAdapter: EntityAdapter<Customer> = createEntityAdapter<
  Customer
>();

export const defaultCustomer: CustomerState = {
  ids: [],
  entities: {},
  selectedCustomerId: null,
  loading: false,
  loaded: false,
  error: ""
};

export const initialState = customerAdapter.getInitialState(defaultCustomer); 
...
// (to be continued)

We’re creating the CustomerState by extending the EntityState. Our new CustomerState will then contain the properties id and entities. We’re also adding the selectedCustomerId property that we’ll need in a later video.

Then we need to create an instance of the entity adapter. This provides us with useful methods and will reduce the amount of boilerplate code we have to write.

We define a defaultCustomer and then create the initialState for our application. As we need it to have the properties id and entities, we’re doing that by using the getInitialState() method of the entity adapter (customerAdapter). This method will take the defaultCustomer and do the logic to return the initial state the way we need it.

Now we can modify our reducer function to use the customerAdapter methods:

// customers/customer.reducer.ts (continued)
...
export function customerReducer(
  state = initialState,
  action: customerActions.Action
): CustomerState {
  switch (action.type) {
    case customerActions.CustomerActionTypes.LOAD_CUSTOMERS: {
      return {
        ...state,
        loading: true,
        loaded: false
      };
    }

    case customerActions.CustomerActionTypes.LOAD_CUSTOMERS_SUCCESS: {
      // use the adapter to load all the entities in the store
      return customerAdapter.addAll(action.payload, {
        ...state,
        loading: false,
        loaded: true
      });
    }

    case customerActions.CustomerActionTypes.LOAD_CUSTOMERS_FAIL: {
      return {
        ...state,
        entities: {},
        loading: false,
        loaded: false,
        error: action.payload
      };
    }

    default: {
      return state;
    }
  }
}
...
// (to be continued)

As we can see here, we’re using the .addAll() method provided by the entity adapter (the customerAdapter). This helps us to load the data in our store.

And last, we’re going to change our selector to use the getSelectors() method provided by the adapter. This method provides functions for selecting information from the entity:

// customers/customer.reducer.ts (continued) ...
const getCustomerFeatureState = createFeatureSelector<CustomerState>(
  "customers"
);

export const getCustomers = createSelector(
  getCustomerFeatureState, 
  customerAdapter.getSelectors().selectAll
);

export const getCustomersLoading = createSelector(
  getCustomerFeatureState,
  (state: CustomerState) => state.loading
);

export const getCustomersLoaded = createSelector(
  getCustomerFeatureState,
  (state: CustomerState) => state.loaded
);

export const getError = createSelector(
  getCustomerFeatureState,
  (state: CustomerState) => state.error
);
... // (to be continued)

Here, we’re using the selectAll() function to select all the entities in the feature state.

So now our component will receive the information correctly. Refresh the page. The component is now displaying the data in the template. If we inspect the store we’ll see that it now contains the ids and entities properties in the customer state.

In the next video, we’re going to continue to add the full CRUD.