When using NgRx to build our application, one of the first things that we have to do is to decide what is the best possible format for storing data inside the store.

Handling the business data in our centralized store is something that we will need to do in any NgRx application, but the process can be repetitive and time-consuming if we have to come up with our own ad-hoc solution.

We often find ourselves handwriting the exact same reducer logic and selectors for different types of data, which is error prone and slows down the development process.

In this post, we are going to learn how NgRx Entity really helps us to handle the business data in our store.

We are going to understand in detail what is the value proposition of NgRx Entity and of the Entity State format that it uses, we will learn exactly what problem NgRx Entity solves and know when to use it and why.

Table of Contents

In this post, we will cover the following topics:

  • What is an Entity?
  • How to store collections of entities in a store?
  • designing the entity store state: Arrays or Maps?
  • What is NgRx Entity, when to use it?
  • The NgRx Entity Adapter
  • Defining the default entity sort order
  • Defining the Entity initial state
  • Write simpler reducers with NgRx Entity
  • Using NgRx Entity Selectors
  • What NgRx Entity is not designed to do
  • Configuring a custom unique ID field
  • Scaffolding an Entity using NgRx Schematics
  • The NgRx Entity Update<T> type
  • Github repo with running example
  • Conclusions

Note that this post builds on other store concepts such as actions, reducers and selectors. If you are looking for an introduction to NgRx Store and to the store architecture in general, have a look at this post:

Angular Service Layers: Redux, RxJs and Ngrx Store - When to Use a Store And Why?.

If you are looking for a guide to help setup the NgRx development environment, including DevTools, the time travelling debugger with router integration and NgRx Store Freeze, have a look at:

Angular Ngrx DevTools: Important Practical Tips.

So without further ado, let's get started in our NgRx Entity deep dive! Let's start at the beginning and start by understanding first what is an entity.

What is an Entity?

In NgRx, we store different types of state in the store, and this typically includes:

  • business data, such as for example Courses or Lessons, in the case of an online course platform

  • some UI state, such as for example UI user preferences

An Entity represents some sort of business data, so Course and Lesson are examples of entity types.

In our code, an entity is defined as a Typescript type definition. For example, in an online course system, the most important entities would be Course and Lesson, defined with these two custom object types:

export interface Course {
id:number;
description:string;
iconUrl?: string;
courseListIcon?: string;
longDescription?: string;
category:string;
seqNo: number;
lessonsCount?:number;
promo?:boolean;
}
export interface Lesson {
id: number;
description: string;
duration: string;
seqNo: number;
courseId?: number;
videoId?: string;
}
view raw 01.ts hosted with ❤ by GitHub

The Entity unique identifier

As we can see, both entities have a unique identifier field called id, which can be either a string or a number. This is a technical identifier that is unique to a given instance of the entity: for example, no two courses have the same id.

Most of the data that we store in the store are entities!

How to store collections of entities in a store?

Let's say that for example, we would like to store a collection of courses in the in-memory store: how would we do that? One way would be to store the courses in an array, under a courses property.

The complete store state would then look something like this:

{
courses: [
{
id: 0,
description: "Angular Ngrx Course",
category: 'BEGINNER',
seqNo: 1
},
{
id: 1,
description: "Angular for Beginners",
category: 'BEGINNER',
seqNo: 2
},
{
id: 2,
description: 'Angular Security Course - Web Security Fundamentals',
category: 'ADVANCED',
seqNo: 3
},
...
],
lessons: [
{
id: 1,
"description": "Angular Tutorial For Beginners - Build Your First App - Hello World Step By Step",
"duration": "4:17",
"seqNo": 1,
courseId: 1
},
{
id: 2,
"description": "Building Your First Component - Component Composition",
"duration": "2:07",
"seqNo": 2,
courseId: 1
},
...
]
}
view raw 02.ts hosted with ❤ by GitHub

Storing entities in the store in the form of an array is the first thing that comes to mind, but that approach can cause several potential problems:

  • if we want to look up a course based on it's known id, we would have to loop through the whole collection, which could be inefficient for very large collections

  • more than that, by using an array we could accidentally store different versions of the same course (with the same id) in the array

  • if we store all our entities as arrays, our reducers will look almost the same for every entity

  • For example, take the simple case of adding a new entity to the collection. We would be reimplementing several times the exact same logic for adding a new entity to the collection and reordering the array in order to obtain a certain custom sort order

As we can see, the format under which we store our entities in the store has a big impact on our program.

Let's then try to find out what would be the ideal format for storing entities in the store.

Designing the entity store state: Arrays or Maps?

One of the roles of the store is to act as an in-memory client-side database that contains a slice of the whole database, from which we derive our view models on the client side via selectors.

This works as opposed to the more traditional design that consists in bringing the view model from the server via API calls. Because the store is an in-memory database, it would make sense to store the business entities in their own in-memory database "table", and give them a unique identifier similar to a primary key.

The data can then be flattened out, and linked together using the entity unique identifiers, just like in a database.

A good way of modeling that is to store the entity collection under the form of a Javascript object, which works just like a Map. In this setup, the key of the entity would be the unique id, and the value would be the whole object.

In that new format, this is what the whole store state would look like:

{
courses: {
0: {
id: 0,
description: "Angular Ngrx Course",
category: 'BEGINNER',
seqNo: 1
},
},
1: {
id: 1,
description: "Angular for Beginners",
category: 'BEGINNER',
seqNo: 2
},
2: {
id: 2,
description: "Angular Security Course - Web Security Fundamentals",
category: 'BEGINNER',
seqNo: 3
}
},
lessons: {
1: {
id: 1,
"description": "Angular Tutorial For Beginners - Build Your First App - Hello World Step By Step",
"duration": "4:17",
"seqNo": 1,
courseId: 1
},
2: {
id: 2,
"description": "Building Your First Component - Component Composition",
"duration": "2:07",
"seqNo": 2,
courseId: 1
},
....
35: {
id: 35,
"description": "Unidirectional Data Flow And The Angular Development Mode",
"duration": "7:07",
"seqNo": 6,
courseId: 0
}
}
}
view raw 03.ts hosted with ❤ by GitHub

Designing the state for id lookups

As we can see, this format makes it really simple to lookup entities by id, which is a very common operation. For example, in order to lookup the course with an id of 1, we would simply have to write:

state.courses[1]

It also flattens out the state, making it simpler to combine the multiple entities and 'join' them via a selector query. But there is only one problem: we have lost the information about the order of the collection!

This is because the properties of a Javascript object have no order associated to them, unlike arrays. Is there are any to still store our data by id in a map, and still preserve the information about the order?

Designing the state for preserving entity order

Yes there is, we just have to use both a Map and an Array! We store the objects in a map (called entities), and we store the order information in an array (called ids):

{
courses: {
ids: [0, 1, 2],
entities: {
0: {
id: 0,
description: "Angular Ngrx Course",
category: 'BEGINNER',
seqNo: 1
},
},
1: {
id: 1,
description: "Angular for Beginners",
category: 'BEGINNER',
seqNo: 2
},
2: {
id: 2,
description: "Angular Security Course - Web Security Fundamentals",
category: 'BEGINNER',
seqNo: 3
}
}
},
lessons: {
ids: [1, 2, ... 35],
entities: {
1: {
id: 1,
"description": "Angular Tutorial For Beginners - Build Your First App - Hello World Step By Step",
"duration": "4:17",
"seqNo": 1,
courseId: 1
},
2: {
id: 2,
"description": "Building Your First Component - Component Composition",
"duration": "2:07",
"seqNo": 2,
courseId: 1
},
....
35: {
id: 35,
"description": "Unidirectional Data Flow And The Angular Development Mode",
"duration": "7:07",
"seqNo": 6,
courseId: 0
}
}
}
}
view raw 04.ts hosted with ❤ by GitHub

The Entity State format

This state format, which combines a map of entities with an array of ids is known as the Entity State format.

This is the ideal format for storing business entities in a centralized store, but maintaining this state would represent an extra burden while writing our reducers and selectors, if we would have to write them manually from scratch.

For example, if we would have to write some type definitions to represent the complete store state, they would look something like this:

export interface StoreState {
courses: CourseState:
lessons: LessonsState;
}
export interface CoursesState {
ids: number[];
entities: {[key:number]: Course};
}
export interface LessonsState {
ids: number[];
entities: {[key:number]: Lesson};
}
view raw 05.ts hosted with ❤ by GitHub

As we can see, we already have here some repetition going on, as the types CoursesState and LessonsState are almost identical. More than that, all the reducer and selector code for these two entities would be very similar as well.

Writing reducers that support the Entity State format

Take for example a reducer for a LoadCourse action, that takes the current CoursesState and adds a new course to it and reorders the collection based on the seqNo field.

This is what the reducer logic for the LoadCourse action would look like:

const initialCoursesState: CoursesState = {
ids: [],
entities: {}
}
function sortBySeqNo(e1: Course, e2: Course) {
return e1.seqNo - e2.seqNo;
}
export function coursesReducer(
state = initialCoursesState,
action: CourseActions): CoursesState {
switch (action.type) {
case CourseActionTypes.COURSE_LOADED:
// push new id to array and re-order
const ids = state.ids.slice(0);
ids.push(action.course.id);
ids.sort(sortBySeqNo);
// build a new courses state
return {
ids,
entities: {
...state.entities,
action.payload.course
}
};
default:
return state;
}
}
view raw 06.ts hosted with ❤ by GitHub

As we can see, it's quite some code for simply adding a course to the store. The problem is that we would have to write similar code for other common operations such as updating a course in the store or deleting it.

Avoiding repeated reducer logic

But a bigger problem than that is that the code for an equivalent LoadLesson action, that loads one single Lesson into LessonsState would be nearly identical:

const initialLessonsState: LessonsState = {
ids: [],
entities: {}
}
function sortBySeqNo(e1: Lesson, e2: Lesson) {
return e1.seqNo - e2.seqNo;
}
export function lessonsReducer(
state = initialLessonsState,
action: LessonActions): LessonsState {
switch (action.type) {
case CourseActionTypes.LESSON_LOADED:
// push new id to array and re-order
const ids = state.ids.slice(0);
ids.push(action.lesson.id);
ids.sort(sortBySeqNo);
return {
ids,
entities: {
...state.entities,
action.payload.lesson
}
};
default:
return state;
}
}
view raw 07.ts hosted with ❤ by GitHub

Except for using the type Lesson instead of Course, this code is practically identical to the reducer logic that we wrote before!

As we can see, keeping our entities in this dual array and map scenario gives rise to a lot of repetitive code.

Avoiding repeated Selector logic

More than the repeated type definitions, the repeated initial state, and almost identical reducer logic, we would also have a lot of nearly identical selector logic.

For example, here is some commonly needed selector for the Course entity, that selects all courses available in the store:

export const selectCoursesState =
createFeatureSelector<CoursesState>("courses");
export const selectAllCourses = createSelector(
selectCoursesState,
coursesState => {
const allCourses = Object.values(coursesState.entities)
allCourses.sort(sortBySeqNo);
return allCourses;
}
);
view raw 08.ts hosted with ❤ by GitHub

Quick explanation on feature selectors

Notice the selectCoursesState feature selector, this is an auxiliary selector that simply takes the property courses of the whole store state, like this:

storeState["courses"]

The advantage of using this utility is that this is type safe, and makes it simple to define lazy loaded selectors, that don't have access to the type definition of the root store state.

The selector selectAllCourses gets all the courses in the store and puts them in an array, and sorts the array according to the seqNo field.

The problem is that we would need some nearly identical logic for the Lesson entity:

export const selectLessonsState =
createFeatureSelector<LessonsState>("lessons");
export const selectAllLessons = createSelector(
selectLessonsState,
lessonsState => {
const allLessons = Object.values(lessonsState.entities)
allLessons.sort(sortBySeqNo);
return allLessons;
}
);
view raw 09.ts hosted with ❤ by GitHub

As we can see, this code is almost identical to the selector that we wrote before for the Course entity.

It's a lot of repeated code

Let's summarize what type of code we have seen so far that is almost identical:

  • entity state definitions (like CoursesState and LessonsState)
  • initial reducer state (like initialCoursesState and initialLessonsState)
  • reducer logic
  • selector logic

This is a lot of repeated code just to keep the data in our database in this optimized Entity State format. The problem is, that this is the ideal format for storing related entities in the store, and if we don't use it we will likely end up running into other issues.

The good news is that we can avoid almost all this repeated code by leveraging NgRx Entity!

What is NgRx Entity, when to use it?

NgRx Entity is a small library that helps us to keep our entities in this ideal Entity state format (array of ids plus map of entities).

This library is designed to be used in conjunction with NgRx Store and is really a key part of the NgRx ecosystem. It's just so much better to use NgRx Entity from the start in our project instead of trying to come with our own ad hoc in-memory database format.

Let's now learn the many ways that NgRx Entity helps us to write our NgRx application.

Defining the Entity State

Going back to our Course entity, let's now redefine the entity state using NgRx Entity:

export interface CoursesState extends EntityState<Course> {
}
view raw 10.ts hosted with ❤ by GitHub

This is identical to the type definition we wrote before, but we now don't have to define the ids and entities property for each separate entity. Instead, we can simply inherit from EntityState and have the same result with the same type safety and much less code.

The NgRx Entity Adapter

In order to be able to use the other features of NgRx Entity, we need to first create an entity adapter. The adapter is a utility class that provides a series of utility functions that are designed to make it really simple to manipulate the entity state.

The adapter is what is going to allow us to write all our initial entity state, reducers and selectors in a much simpler way, while still keeping our entity in the standard EntityState format.

Here is the adapter for the Course entity, configured to sort our entities using the seqNo field:

export const adapter : EntityAdapter<Course> =
createEntityAdapter<Course>({
sortComparer: sortBySeqNo
});
view raw 11.ts hosted with ❤ by GitHub

Defining the default entity sort order

Notice here that we have used the optional sortComparer property, that is used to set the sorting order of the Course entity, which is what is going to determine the order of the ids array for this entity.

If we don't use this optional property, then the id field is going to be used to sort the courses.

Write simpler reducers with the NgRx Entity Adapter

Let's now take the adapter and use it to define the initial state that we will need for our reducers.

We will then implement the same reducer logic as before:

export const initialCoursesState: CoursesState =
adapter.getInitialState();
export function lessonsReducer(
state = initialLessonsState,
action: LessonActions): LessonsState {
switch (action.type) {
case CourseActionTypes.LESSON_LOADED:
return adapter.addOne(action.payload.course, state);
default:
return state;
}
}
view raw 15.ts hosted with ❤ by GitHub

Notice how much easier it is now to write our reducer logic using the adapter. The adapter is going to helps us to manipulate the existing CourseState, by doing in the addOne call everything that we were doing before manually:

  • addOne will create a copy of the existing state object, instead of mutating the existing state
  • then addOne is going to create a copy of the ids array and it will add the new course in the correct sort position
  • a copy of the entities object is going to be created, that points to all previous courses objects, without recreating those objects via a deep copy
  • the new entities object will have the new course added

Benefits of using the entity adapter

As we can see, by using the adapter to write our reducers, we can spare a lot of work and avoid common reducer logic bugs, as this type of logic is easy to get wrong.

It's not uncommon to accidentally mutate the store state, which might cause problems especially if we are using OnPush change detection in our application.

Using the adapter prevents all those problems, while reducing a lot the amount of code needed to write our reducers.

Operations supported by the NgRxEntity Adapter

Besides addOne, the NgRx Entity Adapter supports a whole series of common collection modification operations, that we would otherwise have to implement ourselves by hand.

Here is a complete set of examples for all the supported operations:

export function coursesReducer(
state = initialCoursesState,
action: CourseActions): CoursesState {
switch (action.type) {
case CourseActions.ADD_COURSE: {
return adapter.addOne(action.payload.course, state);
}
case CourseActions.UPSERT_COURSE: {
return adapter.upsertOne(action.payload.course, state);
}
case CourseActions.ADD_COURSES: {
return adapter.addMany(action.payload.courses, state);
}
case CourseActions.UPSERT_COURSES: {
return adapter.upsertMany(action.payload.courses, state);
}
case CourseActions.UPDATE_COURSE: {
return adapter.updateOne(action.payload.course, state);
}
case CourseActions.UPDATE_COURSES: {
return adapter.updateMany(action.payload.courses, state);
}
case CourseActions.DELETE_COURSE: {
return adapter.removeOne(action.payload.id, state);
}
case CourseActions.DELETE_COURSES: {
return adapter.removeMany(action.payload.ids, state);
}
case CourseActions.LOAD_COURSES: {
return adapter.addAll(action.payload.courses, state);
}
case CourseActions.CLEAR_COURSES: {
return adapter.removeAll(state);
}
default: {
return state;
}
}
}
view raw 16.ts hosted with ❤ by GitHub

The adapter methods behave in the following way:

  • addOne: add one entity to the collection
  • addMany: add several entities
  • addAll: replaces the whole collection with a new one
  • removeOne: remove one entity
  • removeMany: removes several entities
  • removeAll: clear the whole collection
  • updateOne: Update one existing entity
  • updateMany: Update multiple existing entities
  • upsertOne: Update or Insert one entity
  • upsertMany: Update or Insert multiple entities

Now imagine what it would be if we would have to implement all this reducer logic ourselves!

Using NgRx Entity Selectors

Another thing that NgRx entity helps us with is with commonly needed selectors, such as selectAllCourses and selectAllLessons.

By running the following command, we have available a whole series of commonly needed selectors, generated on the fly:

export const {
selectAll,
selectEntities,
selectIds,
selectTotal
} = adapter.getSelectors();
view raw 17.ts hosted with ❤ by GitHub

These selectors are all ready to be used directly in our components or as the starting point for building other selectors.

Notice that these selectors are all named the same way independently of the entity, so if you need several in the same file, its recommended to import them in the following way:

import * as fromCourses from './courses.reducers';
// this is equivalent to selectAllCourses
// that we wrote manually before
const selectAllCourses = fromCourses.selectAll;
view raw 18.ts hosted with ❤ by GitHub

These selectors are ready to be used and are just as type safe as the ones that we wrote manually ourselves.

What NgRx Entity is not meant to do

Notice that although NgRx Entity made it much easier to write the state, reducer and selector logic of the Course entity, we still had to write the reducer function itself, although this time around in a simpler way using the adapter.

Using NgRx Entity does not avoid having to write reducer logic for each entity, although it makes it much simpler.

This means that for the Lesson entity we would have to do something very similar. The convention is to put all this closely related code that uses the adapter directly in the same file where our entity reducer function is defined.

In the case of the Lesson entity, this is what the complete lesson.reducers.ts file would look like:

export interface LessonsState extends EntityState<Lesson> {
}
export const adapter : EntityAdapter<Lesson> =
createEntityAdapter<Lesson>({sortComparer: sortBySeqNo});
const initialLessonsState = adapter.getInitialState();
export function lessonsReducer(
state = initialLessonsState,
action: Lessonctions): LessonsState {
switch(action.type) {
case CourseActionTypes.LESSON_LOADED:
return adapter.addOne(action.payload.course, state);
default:
return state;
}
}
export const {
selectAll,
selectEntities,
selectIds,
selectTotal
} = adapter.getSelectors();
view raw 19.ts hosted with ❤ by GitHub

In practice, each entity has slightly different reducer logic so there will be no code repetition between reducer functions.

If you are looking for a solution that takes this one step further and removes the need to write entity specific reducer logic, have a look at ngrx-data.

Configuring a custom Unique ID field

As we have mentioned, the entities in our program should all have a technical identifier field called id. But if by some reason this field is either:

  • not available in a given entity
  • or it has a different name
  • or we simply would prefer to use another property which happens to be a natural key

We can still do that by providing a custom id selector function to the adapter. Here is an example:

export const adapter : EntityAdapter<Lesson> =
createEntityAdapter<Lesson>({
sortComparer: sortBySeqNo,
selectId: lesson => lesson.courseId + '-' + lesson.seqNo
});
view raw 20.ts hosted with ❤ by GitHub

This function will be called by the adapter to extract a unique key from a given entity.

In this example, we are creating a unique identifier for the Lesson entity by concatenating the courseIdproperty with the lesson sequential number, which is unique for a given course.

Handling custom state properties

So far we have been defining our entity state only by extending the EntityState type. But its possible that our entity state also has other custom properties other than the standard ids and entities.

Let's say that for the Course entity, we also need an extra flag which indicates if the courses have already been loaded or not. We could define that extra state property in CoursesState, and then update that property in our reducer logic using the adapter.

Here is a complete example of the CoursesState reducer file courses.reducers.ts, now including the extra state property:

export interface CoursesState extends EntityState<Course> {
allCoursesLoaded:boolean;
}
export const adapter : EntityAdapter<Course> =
createEntityAdapter<Course>();
export const initialCoursesState: CoursesState =
adapter.getInitialState({
allCoursesLoaded: false
});
export function coursesReducer(
state = initialCoursesState ,
action: CourseActions): CoursesState {
switch(action.type) {
case CourseActionTypes.COURSE_LOADED:
return adapter.addOne(action.payload.course, state);
case CourseActionTypes.ALL_COURSES_LOADED:
return adapter.addAll(
action.payload.courses, {
...state,
allCoursesLoaded:true
});
default: {
return state;
}
}
}
export const {
selectAll,
selectEntities,
selectIds,
selectTotal
} = adapter.getSelectors();
view raw 21.ts hosted with ❤ by GitHub

Here is what we had to do to include this extra property:

  • first we have added the allCoursesLoaded property to the type definition of CoursesState
  • next, we need to define the initial value of this property in initialCoursesState, by passing an optional object to the call to getInitialState()
  • we now can set this property in our reducer logic, like we are doing here in the ALL_COURSES_LOADED reducer.
  • in order to so, we simple need to make a copy of the CourseState using the spread (... operator), then we modify the property and we pass this new state object to the adapter call

Scaffolding an Entity using NgRx Schematics

If you would like to quickly generate a reducer file like the ones we have shown in this post, you can get a very good starting point by using NgRx Schematics.

The first thing we need to do in order to use entity schematics is to set this CLI property:

ng config cli.defaultCollection @ngrx/schematics

After this, we can now generate a completely new Lesson reducer file by running the following command:

ng generate entity --name Lesson --module courses/courses.module.ts

What does NgRx Entity Schematics generate?

Let's now inspect the output generated by the command above. First, we have an empty Entity model file:

export interface Lesson {
id: string;
}
view raw 22.ts hosted with ❤ by GitHub

This schematic command will also generate a complete action file, with each action corresponding to one state modification method in the entity adapter:

export enum LessonActionTypes {
LoadLessons = '[Lesson] Load Lessons',
AddLesson = '[Lesson] Add Lesson',
UpsertLesson = '[Lesson] Upsert Lesson',
AddLessons = '[Lesson] Add Lessons',
UpsertLessons = '[Lesson] Upsert Lessons',
UpdateLesson = '[Lesson] Update Lesson',
UpdateLessons = '[Lesson] Update Lessons',
DeleteLesson = '[Lesson] Delete Lesson',
DeleteLessons = '[Lesson] Delete Lessons',
ClearLessons = '[Lesson] Clear Lessons'
}
export class LoadLessons implements Action {
readonly type = LessonActionTypes.LoadLessons;
constructor(public payload: { lessons: Lesson[] }) {}
}
export class AddLesson implements Action {
readonly type = LessonActionTypes.AddLesson;
constructor(public payload: { lesson: Lesson }) {}
}
export class UpsertLesson implements Action {
readonly type = LessonActionTypes.UpsertLesson;
constructor(public payload: { lesson: Lesson }) {}
}
export class AddLessons implements Action {
readonly type = LessonActionTypes.AddLessons;
constructor(public payload: { lessons: Lesson[] }) {}
}
export class UpsertLessons implements Action {
readonly type = LessonActionTypes.UpsertLessons;
constructor(public payload: { lessons: Lesson[] }) {}
}
export class UpdateLesson implements Action {
readonly type = LessonActionTypes.UpdateLesson;
constructor(public payload: { lesson: Update<Lesson> }) {}
}
export class UpdateLessons implements Action {
readonly type = LessonActionTypes.UpdateLessons;
constructor(public payload: { lessons: Update<Lesson>[] }) {}
}
export class DeleteLesson implements Action {
readonly type = LessonActionTypes.DeleteLesson;
constructor(public payload: { id: string }) {}
}
export class DeleteLessons implements Action {
readonly type = LessonActionTypes.DeleteLessons;
constructor(public payload: { ids: string[] }) {}
}
export class ClearLessons implements Action {
readonly type = LessonActionTypes.ClearLessons;
}
export type LessonActions =
LoadLessons
| AddLesson
| UpsertLesson
| AddLessons
| UpsertLessons
| UpdateLesson
| UpdateLessons
| DeleteLesson
| DeleteLessons
| ClearLessons;
view raw 23.ts hosted with ❤ by GitHub

Reviewing the content of the Actions file

This file follows the normal recommended structure of an action file:

  • one enum LessonActionTypes with one entry per Lesson action
  • one class per action, with the data passed to the action via a payload property
  • one union type LessonActions at the bottom, with all the action classes of this file

This last union type is especially helpful for writing reducer logic. Thanks to it, we can have full type inference and IDE auto-completion inside the case blocks of our reducers.

The NgRx Entity Update type

Notice also that in the definition of some actions we are using the type Update<Lesson>. This is an auxiliary type provided by NgRx Entity to help model partial entity updates.

This type has a property id that identifies the updated entity, and another property called changesthat specifies what modifications are being made to the entity.

Here is an example of valid update object for the Course type:

const update: Update<Course> = {
id: 1,
changes: {
description: "NgRx In Depth",
category: 'INTERMEDIATE'
}
};
view raw 25.ts hosted with ❤ by GitHub

Reviewing the content of the reducers file

The NgRx Entity schematic command will also generate the Entity reducer file plus a test, as expected. Here is the content of the reducer file:

export interface State extends EntityState<Lesson> {
}
export const adapter: EntityAdapter<Lesson> =
createEntityAdapter<Lesson>();
export const initialState: State =
adapter.getInitialState({});
export function reducer(
state = initialState,
action: LessonActions
): State {
switch (action.type) {
case LessonActionTypes.AddLesson: {
return adapter.addOne(action.payload.lesson, state);
}
case LessonActionTypes.UpsertLesson: {
return adapter.upsertOne(action.payload.lesson, state);
}
case LessonActionTypes.AddLessons: {
return adapter.addMany(action.payload.lessons, state);
}
case LessonActionTypes.UpsertLessons: {
return adapter.upsertMany(action.payload.lessons, state);
}
case LessonActionTypes.UpdateLesson: {
return adapter.updateOne(action.payload.lesson, state);
}
case LessonActionTypes.UpdateLessons: {
return adapter.updateMany(action.payload.lessons, state);
}
case LessonActionTypes.DeleteLesson: {
return adapter.removeOne(action.payload.id, state);
}
case LessonActionTypes.DeleteLessons: {
return adapter.removeMany(action.payload.ids, state);
}
case LessonActionTypes.LoadLessons: {
return adapter.addAll(action.payload.lessons, state);
}
case LessonActionTypes.ClearLessons: {
return adapter.removeAll(state);
}
default: {
return state;
}
}
}
export const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = adapter.getSelectors();
view raw 24.ts hosted with ❤ by GitHub

How to best use the schematics output?

Notice that the generated schematics files (like any other file generated the CLI)
are not meant to remain unchanged.

In fact, you might not even want to use the actions file for example, but instead write your own actions with a given set of conventions like the ones recommended in this talk:

Also, probably not all actions are going to be needed in the application, so it's important to keep only the ones that we need and adapt them. As usual, the files that are generated by schematics are simply a helping starting point that needs to be adapted on a case by case basis.

Github repo with running example

For a complete running example of a small application that shows how to use NgRx Entity with the two entities that we have used in the examples (Course and Lesson), have a look at this repository.

Here are the NgRx DevTools showing the store content with the two entities:

the Ngrx DevTools in action

Conclusions

NgRx Entity is an extremely useful package, but in order to understand it its essential to first be familiar first with the base store concepts like Actions, Reducers and Selectors, and with the store architecture in general.

If we are already familiar with these concepts, we probably already tried to find the best way to structure the data inside our store.

NgRx Entity provides an answer for that by proposing the Entity State format for our business entities, which is optimized for lookups by id while still keeping the entity order information.

The NgRx Entity Adapter together with NgRx Schematics makes it very simple to get started using NgRx Entity to store our data.

But notice that not all store state needs to use NgRx Entity!

NgRx Entity is specifically designed to handle only the business entities in our store, making it simple to store them in memory in a convenient way.

Learn more about the NgRx Ecosystem

I hope that this post helps with getting started with Ngrx Entity, and that you enjoyed it!

If you would like to learn a lot more about NgRx, we recommend checking the NgRx with NgRx Data - The Complete Guide video course, where we will cover the whole NgRx ecosystem in much more detail.

If you are looking to learn how to get started with the NgRx ecosystem, you might want to check the previous blog posts of this series:

Also, if you have some questions or comments please let me know in the comments below and I will get back to you.

To get notified of upcoming posts on Ngrx and other Angular topics, I invite you to subscribe to our newsletter:

If you are just getting started learning Angular, have a look at the Angular for Beginners Course: