The @ngrx/signals/entities plugin makes managing collections of data easier when using NgRx SignalStore. It includes the withEntities feature and several built-in tools, called entity updaters, that help you quickly add, update, or remove items in your data. This plugin simplifies the process of working with groups of items (entities), helping you manage state more efficiently without a lot of extra code, making it a great addition to any NgRx project.
withEntities Feature
The withEntities feature is designed to integrate entity state directly into your store. It ensures that each entity in your collection has a unique identifier, known as the id property, which must be of type EntityId (either a string or a number).
1import { signalStore } from '@ngrx/signals';2import { withEntities } from '@ngrx/signals/entities';3
4type Book = {5 id: string;6 title: string;7 author: string;8 read: boolean;9};10
11export const BooksStore = signalStore(withEntities<Book>());In this example, withEntities helps manage a collection of books, where each book has a unique id, a title, an author, and a read status. The withEntities feature automatically adds several useful signals to the BooksStore:
ids: Signal<EntityId[]>: An array containing all the book IDs.entityMap: Signal<EntityMap<Book>>: A map where each book’s ID is the key, and the book itself is the value.entities: Signal<Book[]>: An array of all the books.
The ids and entityMap signals represent slices of the state, while entities is a computed signal that provides easy access to the entire collection of books. This setup makes it simple to add, update, or remove books from the collection while keeping the state organized and efficient.
Entity Updaters
The @ngrx/signals/entities plugin also comes with a powerful set of standalone entity updaters. These functions are designed to work seamlessly with patchState, making it easier to update your entity collections. Whether you need to add new items, modify existing ones, or remove them, these updaters provide a straightforward and efficient way to manage changes in your entity state.
Add Entity
The addEntity function allows you to add a new entity to your collection. If an entity with the same ID already exists in the collection, the function does nothing—it won’t overwrite the existing entity, and no error is thrown. This is particularly useful for avoiding accidental data loss.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { addEntity, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 addBook(book: Book): void {8 patchState(store, addEntity(book));9 },10 })),11);Add Entities
The addEntities function allows you to add multiple entities to your collection at once. Like addEntity, it ensures that if any of the entities in the collection share the same IDs as the new ones, they won’t be overwritten, and no error will be thrown. This function is especially useful when you need to batch add a group of entities while ensuring existing data remains intact.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { addEntities, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 addBooks(books: Book[]): void {8 patchState(store, addEntities(books));9 },10 })),11);Set Entity
The setEntity function is used to either add a new entity to the collection or replace an existing one with the same ID. Unlike addEntity, which only adds entities if they don’t already exist, setEntity ensures that the entity is added or updated, making it a reliable choice when you want to ensure a specific entity’s data is always current.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { setEntity, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 setBook(book: Book): void {8 patchState(store, setEntity(book));9 },10 })),11);Set Entities
The setEntities function allows you to add or replace multiple entities in your collection at once. This function is particularly useful when you need to ensure that a group of entities is accurately represented in your store, whether by adding new entities or updating existing ones with the same IDs.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { setEntities, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 setBooks(books: Book[]): void {8 patchState(store, setEntities(books));9 },10 })),11);Set All Entities
The setAllEntities function is used to completely replace the current entity collection with a new one. This function is ideal when you need to reset the entire collection to a new state, ensuring that the store only contains the provided entities.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { setAllEntities, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 setAllBooks(books: Book[]): void {8 patchState(store, setAllEntities(books));9 },10 })),11);Update Entity
The updateEntity function allows you to modify an existing entity in the collection based on its ID. It supports partial updates, meaning you can change only specific properties of the entity. If the entity with the specified ID does not exist, no error is thrown, which helps maintain robustness in your state management.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { updateEntity, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 updateBook(bookId: string, changes: Partial<Book>): void {8 patchState(store, updateEntity({ id: bookId, changes }));9 },10 toggleReadStatus(bookId: string): void {11 patchState(12 store,13 updateEntity({14 id: bookId,15 changes: (book) => ({ read: !book.read }),5 collapsed lines
16 }),17 );18 },19 })),20);Update Entities
The updateEntities function allows you to update multiple entities in the collection by specifying their IDs or using a predicate. This function supports partial updates, so you can modify only the properties you need to change. If some of the entities with the given IDs do not exist in the collection, no error will be thrown, which helps to avoid disruptions in your state management.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { updateEntities, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 updateBooksByIds(bookUpdates: { id: string; changes: Partial<Book> }[]): void {8 patchState(store, updateEntities(bookUpdates));9 },10 updateBooksByPredicate(predicate: (book: Book) => boolean, changes: Partial<Book>): void {11 patchState(store, updateEntities({ predicate, changes }));12 },13 })),14);Update All Entities
The updateAllEntities function allows you to apply updates to every entity in the collection. This function supports partial updates, so you can specify which properties to modify while leaving others unchanged. If there are entities in the collection that do not exist or if the collection is empty, no error is thrown, ensuring that the operation is safe and robust.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { updateAllEntities, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 updateAllBooks(changes: Partial<Book>): void {8 patchState(store, updateAllEntities(changes));9 },10 })),11);Remove Entity
The removeEntity function allows you to remove an entity from the collection based on its ID. If the entity with the specified ID does not exist, no error is thrown, making the operation smooth and error-resistant. This function helps manage the state by ensuring that non-existent entities do not cause issues during removal.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { removeEntity, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 removeBook(bookId: string): void {8 patchState(store, removeEntity(bookId));9 },10 })),11);Remove Entities
The removeEntities function allows you to remove multiple entities from the collection based on their IDs or a predicate. This function ensures that if some of the entities with the specified IDs or matching the predicate do not exist, no errors are thrown. It simplifies the process of managing and cleaning up your entity collections.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { removeEntities, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 removeBooksByIds(bookIds: string[]): void {8 patchState(store, removeEntities(bookIds));9 },10 removeBooksByPredicate(predicate: (book: Book) => boolean): void {11 patchState(store, removeEntities({ predicate }));12 },13 })),14);Remove All Entities
The removeAllEntities function allows you to clear out all entities from the collection. This function is useful when you need to reset or empty the entire collection. If the collection is already empty, no error will be thrown, making the operation both safe and straightforward.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import { removeAllEntities, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities<Book>(),6 withMethods((store) => ({7 removeAllBooks(): void {8 patchState(store, removeAllEntities());9 },10 })),11);Custom Entity Identifier
When your entities use a custom identifier other than the default id property, you can specify a custom ID selector. This selector should return either a string or number and helps the NgRx Signal Store manage and identify entities correctly. It’s especially useful for operations like adding, setting, or updating entities.
1import { patchState, signalStore, withMethods } from '@ngrx/signals';2import {3 addEntities,4 removeEntity,5 SelectEntityId,6 setEntity,7 updateAllEntities,8 withEntities,9} from '@ngrx/signals/entities';10
11type Book = {12 isbn: string; // Custom identifier13 title: string;14 author: string;15 read: boolean;22 collapsed lines
16};17
18// Custom ID selector function19const selectId: SelectEntityId<Book> = (book) => book.isbn;20
21export const BooksStore = signalStore(22 withEntities<Book>(),23 withMethods((store) => ({24 addBooks(books: Book[]): void {25 patchState(store, addEntities(books, { selectId }));26 },27 setBook(book: Book): void {28 patchState(store, setEntity(book, { selectId }));29 },30 updateAllBooks(changes: Partial<Book>): void {31 patchState(store, updateAllEntities(changes, { selectId }));32 },33 removeBook(isbn: string): void {34 patchState(store, removeEntity(isbn));35 },36 })),37);The removeEntity function and similar updaters automatically handle the identifier selection, so you do not need to provide a custom ID selector for these operations.
Named Entity Collections
The withEntities feature in NgRx Signal Store allows you to specify a custom prefix for entity properties by providing a collection name. This feature helps in organizing and managing entity collections within the store more effectively, especially when dealing with multiple types of entities.
To use named entity collections, you pass a collection name as an argument to withEntities. This changes the default property names from ids, entityMap, and entities to prefixed versions, like todoIds, todoEntityMap, and todoEntities.
1import { signalStore, type } from '@ngrx/signals';2import { withEntities } from '@ngrx/signals/entities';3
4export const BookStore = signalStore(withEntities({ entity: type<Book>(), collection: 'book' }));Using Named Entity Collections with Updaters
When working with named entity collections, all updaters require the collection name to correctly identify and operate on the entities. Here’s how to define updaters with a named collection:
1import { patchState, signalStore, type, withMethods } from '@ngrx/signals';2import { addEntity, removeEntity, withEntities } from '@ngrx/signals/entities';3
4export const BooksStore = signalStore(5 withEntities({ entity: type<Book>(), collection: 'book' }),6 withMethods((store) => ({7 addBook(book: Book): void {8 patchState(store, addEntity(book, { collection: 'book' }));9 },10 removeBook(id: number): void {11 patchState(store, removeEntity(id, { collection: 'book' }));12 },13 })),14);Managing Multiple Collections
Named entity collections also allow you to manage multiple collections within a single store.
1import { signalStore, type, withMethods } from '@ngrx/signals';2import { addEntity, removeEntity, withEntities } from '@ngrx/signals/entities';3
4type Book = { id: string; title: string; author: string };5type Author = { id: string; name: string };6type Category = { id: string; name: string };7
8export const LibraryStore = signalStore(9 withEntities({ entity: type<Book>(), collection: 'book' }),10 withEntities({ entity: type<Author>(), collection: 'author' }),11 withEntities({ entity: type<Category>(), collection: 'category' }),12 withMethods((store) => ({13 addBook(book: Book): void {14 patchState(store, addEntity(book, { collection: 'book' }));15 },8 collapsed lines
16 addAuthor(author: Author): void {17 patchState(store, addEntity(author, { collection: 'author' }));18 },19 addCategory(category: Category): void {20 patchState(store, addEntity(category, { collection: 'category' }));21 },22 })),23);In this example, the LibraryStore manages three different collections: book, author, and category. Each collection has its own set of properties and methods, allowing for organized and scalable state management.