Skip to content
advanced Phase 12 · NgRx State Management

Entity State and Adapter APIs

Normalize collections into id-keyed maps for fast lookups.

40m
0 problems
Topic Progress 0%

Entity State Normalization

Entity state stores collections as id-keyed maps for constant-time lookups, rather than arrays that require linear search.

Entity State Interface

import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

export interface User {
  id: number;
  name: string;
  email: string;
  isActive: boolean;
}

export interface UserState extends EntityState<User> {
  loading: boolean;
  error: string | null;
}

Creating an Entity Adapter

export const userAdapter: EntityAdapter<User> = createEntityAdapter<User>({
  selectId: (user) => user.id,  // Default: 'id' property
  sortComparer: false,            // Default: no sorting
});

Initial State

export const initialState: UserState = userAdapter.getInitialState({
  loading: false,
  error: null,
});

Adapter State Shape

{
  ids: [1, 2, 3],           // Array of IDs (order preserved)
  entities: {               // Map of id -> entity
    1: { id: 1, name: 'Alice', email: 'alice@example.com', isActive: true },
    2: { id: 2, name: 'Bob', email: 'bob@example.com', isActive: false },
    3: { id: 3, name: 'Charlie', email: 'charlie@example.com', isActive: true },
  },
  loading: false,
  error: null,
}

Adapter Operations and Selectors

EntityAdapter provides methods for common collection operations and generates selectors.

Adapter Methods

// Reducer with adapter operations
export const userReducer = createReducer(
  initialState,

  on(UserActions.loadUsersSuccess, (state, { users }) =>
    userAdapter.setAll(users, { ...state, loading: false })
  ),

  on(UserActions.addUser, (state, { user }) =>
    userAdapter.addOne(user, state)
  ),

  on(UserActions.updateUser, (state, { user }) =>
    userAdapter.upsertOne(user, state)
  ),

  on(UserActions.deleteUser, (state, { id }) =>
    userAdapter.removeOne(id, state)
  ),

  on(UserActions.deleteUsers, (state, { ids }) =>
    userAdapter.removeMany(ids, state)
  ),
);

Adapter Method Reference

Method Purpose
setAll(items, state) Replace entire collection
addOne(item, state) Add single item
addMany(items, state) Add multiple items
upsertOne(item, state) Add or update single item
upsertMany(items, state) Add or update multiple items
updateOne({ id, changes }, state) Update single item
updateMany(updates, state) Update multiple items
removeOne(id, state) Remove single item
removeMany(ids, state) Remove multiple items
removeAll(state) Clear collection

Adapter-Generated Selectors

export const {
  selectIds,      // (state) => state.ids
  selectEntities, // (state) => state.entities
  selectAll,      // (state) => User[]
  selectTotal,    // (state) => number
} = userAdapter.getSelectors(selectUserState);
// In component
users$ = this.store.select(selectAll);
userCount$ = this.store.select(selectTotal);

Why Normalize?

Operation Array Entity (Map)
Find by ID O(n) O(1)
Update item O(n) O(1)
Insert item O(1) O(1)
Delete item O(n) O(1)
Order preservation Yes Via ids array

Quiz

1. What does EntityAdapter store?

Question 1 options

2. What is the benefit of normalized entity state?

Question 2 options

3. Which adapter method adds or updates an item?

Question 3 options

Flashcards

Question

What does EntityAdapter store?

Answer

{ ids: [], entities: { id: entity } } — id-keyed map for O(1) lookups.

Question

Why normalize state?

Answer

Constant-time lookups by ID. O(1) find/update/delete vs O(n) with arrays.

Question

Which method adds or updates?

Answer

upsertOne — adds if new, updates if exists.

Revision Notes

Key Takeaways

  • 1. EntityAdapter normalizes collections into id-keyed maps
  • 2. O(1) lookups vs O(n) with arrays
  • 3. upsertOne adds or updates
  • 4. Adapter generates selectors (selectAll, selectTotal)

Interview Tips

  • • Explain the normalized state shape
  • • Be ready to describe performance benefits
  • • Mention adapter methods for common operations

Cheat Sheet

Entity State Cheat Sheet

Create adapter:

const adapter = createEntityAdapter<User>({
  selectId: (user) => user.id,
});

State shape:

{ ids: [1, 2], entities: { 1: {...}, 2: {...} } }

Operations:

  • setAll: replace collection
  • addOne: add single
  • upsertOne: add or update
  • removeOne: remove by ID

Selectors:

const { selectAll, selectTotal } = adapter.getSelectors(selectUserState);

Continue Learning

Before You Start

Related Reading