Managing Your Collections With the EntityAdapter: @ngrx/entity-Series – Part 1

This three-part series of blogposts is targeted at developers who have already gained experience with NgRx but still manage their collections themselves. In the first part I introduce the Entity Adapter, in the second part I show you how to connect it to NgRx and in the third part how to do it with the Component Store as well.

In this article:

yb
Yannick Baron is architecture consultant at Thinktecture and focuses on Angular and RxJS.

This is the first article of the series about @ngrx/entity.

  1. Managing Your Collections With EntityAdapter
  2. Multiple Entity Collections in the Same Feature State
  3. Using EntityAdapter With ComponentStore

A complaint the NgRx team and all proponents of the NgRx Store have heard at least once, is the one of boilerplate. The team behind NgRx is creating new ways and abstractions to reduce the boilerplate needed for common operations in our projects. In the following I want to highlight the NgRx Entity package which helps with managing a collection of entities.

Managing collections immutably

For the sake of this example let’s create an interface for a Person entity:
				
					interface Person {
    uid: string;
    firstName: string;
    lastName: string;
}
				
			
Typically, when we manage a small collection immutably, we simply make use of a JavaScript array:
				
					let collection: Person[] = [];
				
			
After creating a new object fulfilling the Person interface, we can easily add to our collection:
				
					const newPerson: Person = { uid: 'person-1', firstName: 'Yannik', lastName: 'Baron' };

collection = [...collection, newPerson];
				
			
Ideally, if we know the id property of our entity, we filter out the element with the matching id first in order to avoid duplicates.
 
Updating an entity in place is as simple as mapping the array:
				
					const update: Partial<Person> & { uid: string } = { uid: 'person-1', firstName: 'Yannick' };

collection = collection.map(person => person.uid !== update.uid ? person : { ...person, ...update });
				
			
If you are managing a collection of entities of any kind in your app and follow the immutability principles, you have probably written similar lines of code several times. The operations we want to perform on collections are usually our standard CRUD operations, like adding, inserting, upserting, removing, etc. 
These operations are very simple to implement and follow pretty much the same structure.
 
Collections also have one property that leads us to think of an array or list data structure when implementing. That is the property of ordering. The elements in a collection are usually ordered and ideally, we make sure that we keep that order when inserting or updating entities.

Collections as lists

In computer science, we know that managing a collection in a simple list structure is not
always the most performant way for huge collections and thus other data structures have been born.
Without going too much into detail, as performance and runtime are also highly dependent on the
environment and use of the collection, it is clear that if we want to update, remove or access an
entity, we would need to find it in our array first, which comes with looping through the array.

Also, managing a collection, as stated before, results in a very similar implementation each time.
Therefore, an abstraction can be made, and the @ngrx/entity package and its EntityAdapter was born.

Introducing EntityState and EntityAdapter

Desiring direct access to entities via their id properties leads us to a JavaScript object. In order to
clear the requirement of order, we can simply keep a list of the ids in the order we want the entities to be in. This leads us to managing our collection as such:
				
					interface Collection<T> {
    ids: string[];
    entities: { [id: string]: T | undefined };
}
				
			
In case you have not seen the notation of the entities property above, it is a simple JavaScript object
which has strings as propery names and entites of type T as values.
The @ngrx/entity package provides us with an interface doing exactly that, but it is called EntityState<T>.
We can use that interface essentially anywhere we like to store a collection!
Furthermore, we are provided with an abstraction that lets us perform all operations on the collection
and is appropriately known as EntityAdapter. In typical NgRx fashion we can use a creator function
to create such an adapter and start managing our collection:
				
					// [optional] Id Accessor and Comparer (sort by firstName ascending)
const selectId: IdSelector<Person> = ({ uid }) => uid;
const sortComparer: Comparer<Person> = (p1, p2) => p1.firstName.localeCompare(p2.firstName);

// EntityAdapter
const personAdapter = createEntityAdapter<Person>({ selectId, sortComparer });

// Creating the collection
let collection: EntityState<Person> = personAdapter.getInitialState();

// => { ids: [], entities: {} }
				
			
Using the createEntityAdapter method we create an EntityAdapter for our person entity. In the
above example we also provide a function to map an entity to its it property, which in the case
of Person is the property uid . We are also adding a comparer, to sort the entities by firstName
ascending. If we do not care about the order of our entities and the id property is called id then
providing the respective function becomes optional.

Adding to the collection

Following our example above, let’s add a new person to our collection, making use of the personAdapter:
				
					const newPerson: Person = { uid: "person-1", firstName: "Yannik", lastName: "Baron" };

collection = personAdapter.addOne(newPerson, collection);

// {
//   ids: [ 'person-1' ],
//   entities: {
//     'person-1': { uid: 'person-1', firstName: 'Yannik', lastName: 'Baron' }
//   }
// }
				
			
Please note how we reassign the collection and the addOne method expects the collection as the second
parameter. The EntityAdapter also follows the immutability principle, and as you can see, it has no
dependencies and thus can be used essentially anywhere.
But let’s also showcase a couple more operations, like adding multiple persons and updating one:
				
					// Add many
const newPersons: Person[] = [
  { uid: "person-2", firstName: "Mathias", lastName: "Portmann" },
  { uid: "person-3", firstName: "Rafael", lastName: "Schertius" },
];

collection = personAdapter.addMany(newPersons, collection);


// Update
const update: Update<Person> = {
  id: "person-1",
  changes: { firstName: "Yannick" },
};

collection = personAdapter.updateOne(update, collection);

// {
//   ids: [ 'person-2', 'person-3', 'person-1' ],
//   entities: {
//     'person-2': { uid: 'person-2', firstName: 'Mathias', lastName: 'Portmann' },
//     'person-3': { uid: 'person-3', firstName: 'Rafael', lastName: 'Schertius' },
//     'person-1': { uid: 'person-1', firstName: 'Yannick', lastName: 'Baron' }
//   }
// }
				
			
Take note of the order of the ids. As we provided a comparator, the id of the person Yannick is
last in order, as we are ordering by the firstName property.

 

These operations and many more are available out of the box on the personAdapter and you can find

Accessing the collection

In the case illustrated above, we can easily access the respective properties on our collection data structure. However, we should make use of the adapter as we shouldn’t really have to care too much about how the data is stored for us.

 

Using the EntityAdapter‘s getSelectors() method, we are provided with selectors to read from our
collection. A selector is a pure mapping function that in our case accepts the collection and returns the
relevant information. This is in line with the principles of the NgRx Store.

 

Let’s select our entities in order and also get the total count:
				
					const { selectAll, selectTotal } = personAdapter.getSelectors();

console.log(selectAll(collection));
// [
//   { uid: 'person-2', firstName: 'Mathias', lastName: 'Portmann' },
//   { uid: 'person-3', firstName: 'Rafael', lastName: 'Schertius' },
//   { uid: 'person-1', firstName: 'Yannick', lastName: 'Baron' }
// ]

console.log(selectTotal(collection));
// 3
				
			
We can see that using the selectAll selector, returns us a list of our entities in order of the ids array.

 

Furthermore, selectors to retrieve our entities object as well as the list of ids are available to us as well:
				
					const { selectEntities, selectIds } = personAdapter.getSelectors();

console.log(selectEntities(collection)['person-1']);
// { uid: 'person-1', firstName: 'Yannick', lastName: 'Baron' }

console.log(selectIds(collection));
// [ 'person-2', 'person-3', 'person-1' ]
				
			

Conclusion

The above post introduces the @ngrx/entity package outside of its use with @ngrx/store. We can see
that managing a collection can easily be achieved using the EntityAdapter. Due to not having any
dependencies we can make use of this collection management utility in various contexts. If we need
to handle multiple collections, we can easily create multiple adapters. Also, the rich API makes
updating our collection fairly simple and we can save a lot of boilerplate implementations in CRUD
heavy applications.

Stay tuned for the next posts in this series where we elaborate on using the EntityAdapter with
the @ngrx/store as well as the @ngrx/component-store!
Free
Newsletter

Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

EN Newsletter Anmeldung (#7)
Related Articles
AI
sg
One of the more pragmatic ways to get going on the current AI hype, and to get some value out of it, is by leveraging semantic search. This is, in itself, a relatively simple concept: You have a bunch of documents and want to find the correct one based on a given query. The semantic part now allows you to find the correct document based on the meaning of its contents, in contrast to simply finding words or parts of words in it like we usually do with lexical search. In our last projects, we gathered some experience with search bots, and with this article, I'd love to share our insights with you.
17.05.2024
Angular
SL-rund
If you previously wanted to integrate view transitions into your Angular application, this was only possible in a very cumbersome way that needed a lot of detailed knowledge about Angular internals. Now, Angular 17 introduced a feature to integrate the View Transition API with the router. In this two-part series, we will look at how to leverage the feature for route transitions and how we could use it for single-page animations.
15.04.2024
.NET
KP-round
.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.
15.11.2023