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:
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:
Why not store related entities in an Array?
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:
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
):
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:
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:
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:
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:
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:
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
andLessonsState
) - initial reducer state (like
initialCoursesState
andinitialLessonsState
) - 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:
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:
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:
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 theids
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:
The adapter methods behave in the following way:
addOne
: add one entity to the collectionaddMany
: add several entitiesaddAll
: replaces the whole collection with a new oneremoveOne
: remove one entityremoveMany
: removes several entitiesremoveAll
: clear the whole collectionupdateOne
: Update one existing entityupdateMany
: Update multiple existing entitiesupsertOne
: Update or Insert one entityupsertMany
: 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:
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:
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:
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:
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 courseId
property 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:
Here is what we had to do to include this extra property:
- first we have added the
allCoursesLoaded
property to the type definition ofCoursesState
- next, we need to define the initial value of this property in
initialCoursesState
, by passing an optional object to the call togetInitialState()
- 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:
This schematic command will also generate a complete action file, with each action corresponding to one state modification method in the entity adapter:
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 changes
that specifies what modifications are being made to the entity.
Here is an example of valid update object for the Course
type:
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:
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:
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:
- Angular Service Layers: Redux, RxJs and Ngrx Store - When to Use a Store And Why?
- Angular Ngrx DevTools: Important Practical Tips
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: