0
# Selectors
1
2
Selectors provide efficient, memoized state selection with composition capabilities. They enable derived state computation, performance optimization through memoization, and type-safe state access patterns.
3
4
## Capabilities
5
6
### Create Selector
7
8
Creates memoized selectors that efficiently compute derived state with automatic memoization and composition support.
9
10
```typescript { .api }
11
/**
12
* Creates a memoized selector from input selectors and projector function
13
* @param selectors - Array of input selectors
14
* @param projector - Function that computes the final result
15
* @returns MemoizedSelector with memoization and lifecycle methods
16
*/
17
function createSelector<Selectors extends readonly any[], Result>(
18
selectors: [...Selectors],
19
projector: (...args: SelectorResults<Selectors>) => Result
20
): MemoizedSelector<any, Result>;
21
22
interface MemoizedSelector<State, Result, ProjectorFn = DefaultProjectorFn<Result>>
23
extends Selector<State, Result> {
24
/** Release memoized values and reset internal state */
25
release(): void;
26
/** The projector function used to compute the result */
27
projector: ProjectorFn;
28
/** Override the result value (useful for testing) */
29
setResult: (result?: Result) => void;
30
/** Clear any overridden result value */
31
clearResult: () => void;
32
}
33
```
34
35
**Usage Examples:**
36
37
```typescript
38
import { createSelector } from "@ngrx/store";
39
40
interface AppState {
41
users: UserState;
42
orders: OrderState;
43
ui: UiState;
44
}
45
46
// Feature selectors
47
const selectUserState = (state: AppState) => state.users;
48
const selectOrderState = (state: AppState) => state.orders;
49
50
// Basic selector
51
const selectAllUsers = createSelector(
52
selectUserState,
53
(userState) => userState.entities
54
);
55
56
// Composed selector with multiple inputs
57
const selectActiveUserOrders = createSelector(
58
selectAllUsers,
59
selectOrderState,
60
(users, orderState, props: { userId: string }) => {
61
const user = users.find(u => u.id === props.userId);
62
return user?.active ? orderState.entities.filter(o => o.userId === props.userId) : [];
63
}
64
);
65
66
// Complex derived state
67
const selectOrderSummary = createSelector(
68
selectOrderState,
69
selectAllUsers,
70
(orderState, users) => {
71
const orders = orderState.entities;
72
const totalRevenue = orders.reduce((sum, order) => sum + order.total, 0);
73
const ordersByStatus = orders.reduce((acc, order) => {
74
acc[order.status] = (acc[order.status] || 0) + 1;
75
return acc;
76
}, {} as Record<string, number>);
77
78
return {
79
totalOrders: orders.length,
80
totalRevenue,
81
averageOrderValue: totalRevenue / orders.length || 0,
82
ordersByStatus,
83
topCustomers: users
84
.map(user => ({
85
...user,
86
orderCount: orders.filter(o => o.userId === user.id).length
87
}))
88
.sort((a, b) => b.orderCount - a.orderCount)
89
.slice(0, 5)
90
};
91
}
92
);
93
```
94
95
### Create Feature Selector
96
97
Creates selectors for accessing feature state slices in the store.
98
99
```typescript { .api }
100
/**
101
* Creates a selector for a specific feature slice of state
102
* @param featureKey - The key of the feature in the state
103
* @returns MemoizedSelector for the feature state
104
*/
105
function createFeatureSelector<T, K extends keyof T>(
106
featureKey: K
107
): MemoizedSelector<T, T[K]>;
108
```
109
110
**Usage Examples:**
111
112
```typescript
113
import { createFeatureSelector, createSelector } from "@ngrx/store";
114
115
// Create feature selectors
116
const selectUserFeature = createFeatureSelector<AppState, 'users'>('users');
117
const selectOrderFeature = createFeatureSelector<AppState, 'orders'>('orders');
118
const selectUiFeature = createFeatureSelector<AppState, 'ui'>('ui');
119
120
// Use feature selectors as base for other selectors
121
const selectUserList = createSelector(
122
selectUserFeature,
123
(userState) => userState.entities
124
);
125
126
const selectCurrentUser = createSelector(
127
selectUserFeature,
128
(userState) => userState.entities.find(u => u.id === userState.selectedUserId)
129
);
130
131
const selectIsLoading = createSelector(
132
selectUserFeature,
133
selectOrderFeature,
134
(userState, orderState) => userState.loading || orderState.loading
135
);
136
```
137
138
### Create Selector Factory
139
140
Creates custom selector factories with configurable memoization strategies.
141
142
```typescript { .api }
143
/**
144
* Creates a selector factory with custom memoization
145
* @param memoize - Custom memoization function
146
* @returns SelectorFactory for creating selectors with custom memoization
147
*/
148
function createSelectorFactory<T, V>(memoize: MemoizeFn): SelectorFactory<T, V>;
149
150
/**
151
* Function that adds memoization to any function
152
*/
153
type MemoizeFn = (fn: AnyFn) => MemoizedProjection;
154
155
/**
156
* Comparison function for memoization
157
*/
158
type ComparatorFn = (a: any, b: any) => boolean;
159
160
interface MemoizedProjection {
161
memoized: AnyFn;
162
reset: () => void;
163
setResult: (result?: any) => void;
164
clearResult: () => void;
165
}
166
```
167
168
**Usage Examples:**
169
170
```typescript
171
import { createSelectorFactory, defaultMemoize, resultMemoize } from "@ngrx/store";
172
173
// Custom memoization that only checks result equality
174
const createDeepEqualSelector = createSelectorFactory(
175
(projectionFn) => resultMemoize(projectionFn, deepEqual)
176
);
177
178
// Selector with custom memoization
179
const selectComplexData = createDeepEqualSelector(
180
selectUserState,
181
selectOrderState,
182
(userState, orderState) => {
183
// Expensive computation that returns complex object
184
return computeComplexAnalytics(userState, orderState);
185
}
186
);
187
188
// No memoization selector for always-fresh data
189
const createNonMemoizedSelector = createSelectorFactory(
190
(projectionFn) => ({
191
memoized: projectionFn,
192
reset: () => {},
193
setResult: () => {},
194
clearResult: () => {}
195
})
196
);
197
198
const selectCurrentTime = createNonMemoizedSelector(
199
selectUiState,
200
(uiState) => ({
201
...uiState.timeConfig,
202
currentTime: Date.now()
203
})
204
);
205
```
206
207
### Default Memoization Functions
208
209
Built-in memoization strategies for different use cases.
210
211
```typescript { .api }
212
/**
213
* Default memoization function with argument and result equality checking
214
* @param projectionFn - Function to memoize
215
* @param isArgumentsEqual - Function to compare arguments (default: strict equality)
216
* @param isResultEqual - Function to compare results (default: strict equality)
217
* @returns MemoizedProjection with reset and override capabilities
218
*/
219
function defaultMemoize(
220
projectionFn: AnyFn,
221
isArgumentsEqual?: ComparatorFn,
222
isResultEqual?: ComparatorFn
223
): MemoizedProjection;
224
225
/**
226
* Result-based memoization that only checks result equality
227
* @param projectionFn - Function to memoize
228
* @param isResultEqual - Function to compare results
229
* @returns MemoizedProjection focused on result comparison
230
*/
231
function resultMemoize(
232
projectionFn: AnyFn,
233
isResultEqual: ComparatorFn
234
): MemoizedProjection;
235
236
/**
237
* Default state function (identity function)
238
*/
239
function defaultStateFn<T>(state: T): T;
240
241
/**
242
* Default equality check using strict equality
243
*/
244
function isEqualCheck(a: any, b: any): boolean;
245
```
246
247
## Core Type Definitions
248
249
### Selector Types
250
251
```typescript { .api }
252
/**
253
* Function that selects a value from state
254
*/
255
type Selector<T, V> = (state: T) => V;
256
257
/**
258
* @deprecated Selector with additional props parameter
259
*/
260
type SelectorWithProps<State, Props, Result> = (state: State, props: Props) => Result;
261
262
/**
263
* Default projector function type
264
*/
265
type DefaultProjectorFn<T> = (...args: any[]) => T;
266
267
/**
268
* Any function type for memoization
269
*/
270
type AnyFn = (...args: any[]) => any;
271
```
272
273
## Advanced Usage Patterns
274
275
### Performance-Optimized Selectors
276
277
```typescript
278
import { createSelector, createSelectorFactory, defaultMemoize } from "@ngrx/store";
279
280
// Custom memoization for expensive computations
281
const createExpensiveSelector = createSelectorFactory(
282
(projectionFn) => defaultMemoize(
283
projectionFn,
284
// Custom argument equality - only recompute if IDs change
285
(argsA, argsB) => {
286
return argsA[0]?.id === argsB[0]?.id && argsA[1]?.version === argsB[1]?.version;
287
},
288
// Custom result equality - deep comparison
289
(a, b) => JSON.stringify(a) === JSON.stringify(b)
290
)
291
);
292
293
const selectExpensiveCalculation = createExpensiveSelector(
294
selectLargeDataset,
295
selectConfiguration,
296
(dataset, config) => {
297
// Expensive computation
298
return performComplexAnalysis(dataset, config);
299
}
300
);
301
```
302
303
### Parameterized Selectors
304
305
```typescript
306
// Selector factory pattern for parameterized selection
307
const makeSelectUserById = () => createSelector(
308
selectAllUsers,
309
(users, props: { id: string }) => users.find(user => user.id === props.id)
310
);
311
312
const makeSelectUserOrders = () => createSelector(
313
selectAllOrders,
314
(orders, props: { userId: string }) => orders.filter(order => order.userId === props.userId)
315
);
316
317
// Usage in components
318
class UserComponent {
319
@Input() userId!: string;
320
321
private selectUser = makeSelectUserById();
322
private selectOrders = makeSelectUserOrders();
323
324
user$ = this.store.select(this.selectUser, { id: this.userId });
325
orders$ = this.store.select(this.selectOrders, { userId: this.userId });
326
}
327
```
328
329
### Conditional Selectors
330
331
```typescript
332
const selectConditionalData = createSelector(
333
selectUserState,
334
selectAppConfig,
335
(userState, config) => {
336
// Conditional logic in selector
337
if (config.featureFlags.advancedMode) {
338
return {
339
...userState,
340
advancedMetrics: computeAdvancedMetrics(userState.entities),
341
recommendations: generateRecommendations(userState.entities)
342
};
343
}
344
345
return {
346
basicData: userState.entities.map(user => ({
347
id: user.id,
348
name: user.name,
349
status: user.status
350
}))
351
};
352
}
353
);
354
```
355
356
### Selector Composition and Reuse
357
358
```typescript
359
// Base selectors
360
const selectActiveUsers = createSelector(
361
selectAllUsers,
362
users => users.filter(user => user.active)
363
);
364
365
const selectPremiumUsers = createSelector(
366
selectAllUsers,
367
users => users.filter(user => user.subscription === 'premium')
368
);
369
370
// Composed selectors
371
const selectActivePremiumUsers = createSelector(
372
selectActiveUsers,
373
selectPremiumUsers,
374
(activeUsers, premiumUsers) =>
375
activeUsers.filter(user => premiumUsers.some(p => p.id === user.id))
376
);
377
378
const selectUserStatistics = createSelector(
379
selectAllUsers,
380
selectActiveUsers,
381
selectPremiumUsers,
382
(allUsers, activeUsers, premiumUsers) => ({
383
total: allUsers.length,
384
active: activeUsers.length,
385
premium: premiumUsers.length,
386
activeRatio: activeUsers.length / allUsers.length,
387
premiumRatio: premiumUsers.length / allUsers.length
388
})
389
);
390
```
391
392
## Testing Selectors
393
394
```typescript
395
import { selectUserSummary } from './user.selectors';
396
397
describe('User Selectors', () => {
398
it('should select user summary', () => {
399
const mockState = {
400
users: {
401
entities: [
402
{ id: '1', name: 'John', active: true },
403
{ id: '2', name: 'Jane', active: false }
404
]
405
}
406
};
407
408
const result = selectUserSummary(mockState);
409
410
expect(result).toEqual({
411
total: 2,
412
active: 1,
413
inactive: 1
414
});
415
});
416
417
it('should memoize results', () => {
418
const state1 = { users: { entities: [] } };
419
const state2 = { users: { entities: [] } };
420
421
const result1 = selectUserSummary(state1);
422
const result2 = selectUserSummary(state2);
423
424
// Same reference due to memoization
425
expect(result1).toBe(result2);
426
});
427
});
428
```
429
430
## Best Practices
431
432
1. **Use Feature Selectors**: Start with `createFeatureSelector` for clear state structure
433
2. **Compose Selectors**: Build complex selectors from simpler ones for reusability
434
3. **Memoization Strategy**: Choose appropriate memoization based on data characteristics
435
4. **Pure Functions**: Keep selector functions pure and side-effect free
436
5. **Performance**: Use custom memoization for expensive computations
437
6. **Testing**: Test selectors independently with mock state data