Prepare for the wonders of the ‘entity pattern’ in Angular.
“What are entities in Angular?” I hear you ask. It’s a pattern for state management, and we’re going to explore how it’ll help you developer faster and cleaner applications.
Theory of the Entity Pattern
Firstly, I want to discuss how it affects performance on a higher level. Entities are essentially your data, but flattened.
Imagine you have 1,000 items inside an array, and want to render those out for a user. Now imagine one of those items changes, perhaps you update a property somewhere in your data structure.
What would that involve? Iterating (looping) through your entire data structure to find that item, update it and then create a new collection once finished.
The updating part isn’t necessarily the issue, it’s the fact that we’d have to loop the data time and time again to perform simple operations - creating, updating, deleting, you name it.
Secondly, updating entities is actually easier because your data is flat and driven by a unique key. This makes finding the item faster, but also makes the code to update itt simpler too.
With this conceptual piece in mind, let’s investigate the implementation.
For this, it really doesn’t matter if you’re rolling your own state management, using a library such as NGRX Store and NGRX Entity, the theory is the same.
🤔 Interesting: About 1/2 of users using
@ngrx/store
do NOT use the@ngrx/entity
package (based onnpm
download data).
Faster JavaScript References
Now onto the “why use the entity pattern” part - performance. Even with a very simple example the performance is easy to visualize.
Let’s assume the basic interface
below and state
:
interface Task {
id: string;
title: string,
members: string[];
}
interface TaskState {
tasks: Task[];
}
const state: TaskState = {
tasks: [
{ id: 'aX5Hj9', title: 'Visit Pokémart', members: ['blastoise', 'haunter'] },
{ id: 'e4Gf9a', title: 'Climb Pokémon Tower', members: ['articuno', 'pikachu'] },
{ id: 't3lP01', title: 'Update Pokédex', members: ['ash', 'oak'] },
]
};
Arrays are a great object in JavaScript, we use them all the time, they make sense.
If we had thousands of tasks
though, this would be a performance bottleneck to just make a simple lookup to get a single item.
Not only this, managing the state for the members would also be a little verbose too…
Without the entity pattern, here’s how we could access the id
of e4Gf9a
:
const id = 'e4Gf9a';
const { tasks } = state;
// { id: 'e4Gf9a', title: 'Climb Pokémon Tower', members: ['articuno', 'pikachu'] }
const task = tasks.find(task => task.id === id);
For 3
array elements, this is going to be pretty fast…
But for larger collections, it’s a repetitive task that just takes up memory allocation to find a single item. It’s not like it’s a resource-heavy task either is it?
Not only that, but it’s more logic we have to write, test, and potentially debug.
So how does the entity pattern help us? An entity is simply each individual array element, flattened.
Refactoring our interface
and state
, we could describe an entity approach and flatten the data, indexing each entity via it’s unique id
key:
interface Task {
id: string;
title: string,
members: string[];
}
interface TaskState {
entities: { [id: string] Task };
}
const state: TaskState = {
entities: {
'aX5Hj9': { id: 'aX5Hj9', title: 'Visit Pokémart', members: ['blastoise', 'haunter'] },
'e4Gf9a': { id: 'e4Gf9a', title: 'Climb Pokémon Tower', members: ['articuno', 'pikachu'] },
't3lP01': { id: 't3lP01', title: 'Update Pokédex', members: ['charizard', 'alakazam'] },
}
};
Okay, looks kind of similar - so what? The idea here is that the entities
and just an object. Object lookups are very fast.
Let’s say we want to access the item with the id
of e4Gf9a
, how would it work?
const id = 'e4Gf9a';
const { entities } = state;
// { id: 'e4Gf9a', title: 'Climb Pokémon Tower', members: ['articuno', 'pikachu'] }
const task = entities[id];
Now we’re talking. That’s insanely fast for the JavaScript engine to lookup your reference and bring it back to you.
Simpler code, faster code.
“How do we render each entity as a list?” I hear you ask.
It’s a common practice to make a single “selector” to get all your entities
and return them as an array for rendering:
const { entities } = state;
// Array(3) [{...}, {...}, {...}]
const allTasks = Object.keys(entities).map(id => entities[id]);
Anytime you update an entity, the allTasks
can just be queried again to get the latest state snapshot.
Mapping Arrays to Entities
So far we’ve looked at manually creating an entities
property on our state tree, but how could we handle it when fetching data over the network via an API?
For this, it involves a single loop when your data is returned, and setting it flattened in the store.
We can turn to Array.reduce to achieve this easily:
const toEntities = (collection) => {
return collection.reduce((prev, next) => ({
...prev,
[next.id]: next
}), {});
};
const data = [
{ id: 'aX5Hj9', title: 'Visit Pokémart', members: ['blastoise', 'haunter'] },
{ id: 'e4Gf9a', title: 'Climb Pokémon Tower', members: ['articuno', 'pikachu'] },
{ id: 't3lP01', title: 'Update Pokédex', members: ['charizard', 'alakazam'] },
];
/*
{
'aX5Hj9': { id: 'aX5Hj9', title: 'Visit Pokémart', members: ['blastoise', 'haunter'] },
'e4Gf9a': { id: 'e4Gf9a', title: 'Climb Pokémon Tower', members: ['articuno', 'pikachu'] },
't3lP01': { id: 't3lP01', title: 'Update Pokédex', members: ['charizard', 'alakazam'] },
}
*/
const entities = toEntities(data);
console.log(entities);
The magic ingredient here is using an empty object {}
as the initial argument to the reduce
function.
From here, you can set your entities
in your state tree.
Creating or Updating Entities
Interestingly, the way you create and update an entity is exactly the same. That’s not the case with an array collection (again, more code to test and operations to perform).
With an array we would create a new item like this:
// create a new array element
const newTask = { id: 'cV6Kh2', title: 'Journey to Indigo Plateau', members: ['ash', 'misty'] }
const newState = [...state.tasks, newTask];
This involves spreading in all existing array items, following immutable good practice, to merge in existing data and append the new item.
Creating a new entity is easy, just add it to the entities
object:
// create a new entity
const newTask = { id: 'cV6Kh2', title: 'Journey to Indigo Plateau', members: ['ash', 'misty'] }
const newState = { ...entities, [newTask.id]: newTask };
Again we copy all existing state to create a new reference, but this approach feels simpler.
When we want to update an array element versus entity, the two approaches are not so similar:
// update an array element
const updatedTask = { id: 'cV6Kh2', title: 'Journey to Indigo Plateau', members: [] }
const newState = state.tasks.map(task => {
if (task.id === updatedTask.id) {
return updatedTask;
}
return task;
});
The above will iterate our entire data collection everytime something updates… not very performant!
This is where the entity pattern shines:
// update an entity
const updatedTask = { id: 'cV6Kh2', title: 'Journey to Indigo Plateau', members: ['ash', 'misty'] }
const newState = { ...entities, [updatedTask.id]: updatedTask };
No need to “find the item” and replace it, just simply reference it’s id
property and replace it in a single line of code.
Again, to create and update are the same operations! Less code to write, test, and it’s faster.
Deleting Entities
Deleting is nice and easy and can be achieved with a nice trick using the spread operator alongside some destructuring. But first, let’s look at deleting an array element from our state.
This is typically done by just passing the id
to lookup the item and delete it:
// delete an array item via `id`
const taskToDelete = 'cV6Kh2';
const newState = state.tasks.filter(task => task.id !== taskToDelete);
Another big loop to just remove an item.
Here’s my trick using entities:
// delete an entity
const taskToDelete = { id: 'cV6Kh2', title: 'Journey to Indigo Plateau', members: ['ash', 'misty'] }
const { [taskToDelete.id]: removed, ...entities } = state;
const newState = { entities };
There’s a little bit of magic happening here. We essentially destructure state
into two parts…
The first, we ask for the dynamic property [taskToDelete.id]
which fetches the item from state
and assigns it under a new variable removed
. This is a naming convention I like to use, but call it anything you like.
Secondly, we spread the rest of the remaining ...entities
. This creates another local variable which we just assign to the new state tree.
Rendering Order of Entities
A JavaScript object does not guarantee the rendering order of keys, so a common practice is to keep an array of ids
alongside your entities
so you can perform tasks like reordering, sorting, etc. From there, you can then do something like:
const { entities, ids } = state;
// Array(3) [{...}, {...}, {...}]
const allTasks = ids.map(id => entities[id]);
State Management Libraries
You can adopt a library such as NGRX Store with the Entity package to achieve this, or perhaps look at my new library @ultimate/lite-store which is a super minimal reactive state solution for Angular - with full entity support and frozen state out the box.
Conclusion
Using an Entity Pattern makes sense, even in smaller applications! It focuses on just raw objects and takes the headaches away from managing state with arrays.
🚀 Learn more techniques, best practices and real-world expert knowledge, I’d highly recommend checking out my Angular courses - they will guide you through your journey to mastering Angular to the fullest!
It’s nice to use a simple selector to get all your ids
and return the necessary entities
. Sort them, delete them, update them - anything is fast and easy.
Entities are a more advanced concept to grasp, so after you’ve got comfortable with Angular and building simple state services, it’s definitely a next-level technique to add another layer of expertise and performance to your app.