RxJS powered Redux state management for Angular applications
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Selectors provide efficient, memoized state selection with composition capabilities. They enable derived state computation, performance optimization through memoization, and type-safe state access patterns.
Creates memoized selectors that efficiently compute derived state with automatic memoization and composition support.
/**
* Creates a memoized selector from input selectors and projector function
* @param selectors - Array of input selectors
* @param projector - Function that computes the final result
* @returns MemoizedSelector with memoization and lifecycle methods
*/
function createSelector<Selectors extends readonly any[], Result>(
selectors: [...Selectors],
projector: (...args: SelectorResults<Selectors>) => Result
): MemoizedSelector<any, Result>;
interface MemoizedSelector<State, Result, ProjectorFn = DefaultProjectorFn<Result>>
extends Selector<State, Result> {
/** Release memoized values and reset internal state */
release(): void;
/** The projector function used to compute the result */
projector: ProjectorFn;
/** Override the result value (useful for testing) */
setResult: (result?: Result) => void;
/** Clear any overridden result value */
clearResult: () => void;
}Usage Examples:
import { createSelector } from "@ngrx/store";
interface AppState {
users: UserState;
orders: OrderState;
ui: UiState;
}
// Feature selectors
const selectUserState = (state: AppState) => state.users;
const selectOrderState = (state: AppState) => state.orders;
// Basic selector
const selectAllUsers = createSelector(
selectUserState,
(userState) => userState.entities
);
// Composed selector with multiple inputs
const selectActiveUserOrders = createSelector(
selectAllUsers,
selectOrderState,
(users, orderState, props: { userId: string }) => {
const user = users.find(u => u.id === props.userId);
return user?.active ? orderState.entities.filter(o => o.userId === props.userId) : [];
}
);
// Complex derived state
const selectOrderSummary = createSelector(
selectOrderState,
selectAllUsers,
(orderState, users) => {
const orders = orderState.entities;
const totalRevenue = orders.reduce((sum, order) => sum + order.total, 0);
const ordersByStatus = orders.reduce((acc, order) => {
acc[order.status] = (acc[order.status] || 0) + 1;
return acc;
}, {} as Record<string, number>);
return {
totalOrders: orders.length,
totalRevenue,
averageOrderValue: totalRevenue / orders.length || 0,
ordersByStatus,
topCustomers: users
.map(user => ({
...user,
orderCount: orders.filter(o => o.userId === user.id).length
}))
.sort((a, b) => b.orderCount - a.orderCount)
.slice(0, 5)
};
}
);Creates selectors for accessing feature state slices in the store.
/**
* Creates a selector for a specific feature slice of state
* @param featureKey - The key of the feature in the state
* @returns MemoizedSelector for the feature state
*/
function createFeatureSelector<T, K extends keyof T>(
featureKey: K
): MemoizedSelector<T, T[K]>;Usage Examples:
import { createFeatureSelector, createSelector } from "@ngrx/store";
// Create feature selectors
const selectUserFeature = createFeatureSelector<AppState, 'users'>('users');
const selectOrderFeature = createFeatureSelector<AppState, 'orders'>('orders');
const selectUiFeature = createFeatureSelector<AppState, 'ui'>('ui');
// Use feature selectors as base for other selectors
const selectUserList = createSelector(
selectUserFeature,
(userState) => userState.entities
);
const selectCurrentUser = createSelector(
selectUserFeature,
(userState) => userState.entities.find(u => u.id === userState.selectedUserId)
);
const selectIsLoading = createSelector(
selectUserFeature,
selectOrderFeature,
(userState, orderState) => userState.loading || orderState.loading
);Creates custom selector factories with configurable memoization strategies.
/**
* Creates a selector factory with custom memoization
* @param memoize - Custom memoization function
* @returns SelectorFactory for creating selectors with custom memoization
*/
function createSelectorFactory<T, V>(memoize: MemoizeFn): SelectorFactory<T, V>;
/**
* Function that adds memoization to any function
*/
type MemoizeFn = (fn: AnyFn) => MemoizedProjection;
/**
* Comparison function for memoization
*/
type ComparatorFn = (a: any, b: any) => boolean;
interface MemoizedProjection {
memoized: AnyFn;
reset: () => void;
setResult: (result?: any) => void;
clearResult: () => void;
}Usage Examples:
import { createSelectorFactory, defaultMemoize, resultMemoize } from "@ngrx/store";
// Custom memoization that only checks result equality
const createDeepEqualSelector = createSelectorFactory(
(projectionFn) => resultMemoize(projectionFn, deepEqual)
);
// Selector with custom memoization
const selectComplexData = createDeepEqualSelector(
selectUserState,
selectOrderState,
(userState, orderState) => {
// Expensive computation that returns complex object
return computeComplexAnalytics(userState, orderState);
}
);
// No memoization selector for always-fresh data
const createNonMemoizedSelector = createSelectorFactory(
(projectionFn) => ({
memoized: projectionFn,
reset: () => {},
setResult: () => {},
clearResult: () => {}
})
);
const selectCurrentTime = createNonMemoizedSelector(
selectUiState,
(uiState) => ({
...uiState.timeConfig,
currentTime: Date.now()
})
);Built-in memoization strategies for different use cases.
/**
* Default memoization function with argument and result equality checking
* @param projectionFn - Function to memoize
* @param isArgumentsEqual - Function to compare arguments (default: strict equality)
* @param isResultEqual - Function to compare results (default: strict equality)
* @returns MemoizedProjection with reset and override capabilities
*/
function defaultMemoize(
projectionFn: AnyFn,
isArgumentsEqual?: ComparatorFn,
isResultEqual?: ComparatorFn
): MemoizedProjection;
/**
* Result-based memoization that only checks result equality
* @param projectionFn - Function to memoize
* @param isResultEqual - Function to compare results
* @returns MemoizedProjection focused on result comparison
*/
function resultMemoize(
projectionFn: AnyFn,
isResultEqual: ComparatorFn
): MemoizedProjection;
/**
* Default state function (identity function)
*/
function defaultStateFn<T>(state: T): T;
/**
* Default equality check using strict equality
*/
function isEqualCheck(a: any, b: any): boolean;/**
* Function that selects a value from state
*/
type Selector<T, V> = (state: T) => V;
/**
* @deprecated Selector with additional props parameter
*/
type SelectorWithProps<State, Props, Result> = (state: State, props: Props) => Result;
/**
* Default projector function type
*/
type DefaultProjectorFn<T> = (...args: any[]) => T;
/**
* Any function type for memoization
*/
type AnyFn = (...args: any[]) => any;import { createSelector, createSelectorFactory, defaultMemoize } from "@ngrx/store";
// Custom memoization for expensive computations
const createExpensiveSelector = createSelectorFactory(
(projectionFn) => defaultMemoize(
projectionFn,
// Custom argument equality - only recompute if IDs change
(argsA, argsB) => {
return argsA[0]?.id === argsB[0]?.id && argsA[1]?.version === argsB[1]?.version;
},
// Custom result equality - deep comparison
(a, b) => JSON.stringify(a) === JSON.stringify(b)
)
);
const selectExpensiveCalculation = createExpensiveSelector(
selectLargeDataset,
selectConfiguration,
(dataset, config) => {
// Expensive computation
return performComplexAnalysis(dataset, config);
}
);// Selector factory pattern for parameterized selection
const makeSelectUserById = () => createSelector(
selectAllUsers,
(users, props: { id: string }) => users.find(user => user.id === props.id)
);
const makeSelectUserOrders = () => createSelector(
selectAllOrders,
(orders, props: { userId: string }) => orders.filter(order => order.userId === props.userId)
);
// Usage in components
class UserComponent {
@Input() userId!: string;
private selectUser = makeSelectUserById();
private selectOrders = makeSelectUserOrders();
user$ = this.store.select(this.selectUser, { id: this.userId });
orders$ = this.store.select(this.selectOrders, { userId: this.userId });
}const selectConditionalData = createSelector(
selectUserState,
selectAppConfig,
(userState, config) => {
// Conditional logic in selector
if (config.featureFlags.advancedMode) {
return {
...userState,
advancedMetrics: computeAdvancedMetrics(userState.entities),
recommendations: generateRecommendations(userState.entities)
};
}
return {
basicData: userState.entities.map(user => ({
id: user.id,
name: user.name,
status: user.status
}))
};
}
);// Base selectors
const selectActiveUsers = createSelector(
selectAllUsers,
users => users.filter(user => user.active)
);
const selectPremiumUsers = createSelector(
selectAllUsers,
users => users.filter(user => user.subscription === 'premium')
);
// Composed selectors
const selectActivePremiumUsers = createSelector(
selectActiveUsers,
selectPremiumUsers,
(activeUsers, premiumUsers) =>
activeUsers.filter(user => premiumUsers.some(p => p.id === user.id))
);
const selectUserStatistics = createSelector(
selectAllUsers,
selectActiveUsers,
selectPremiumUsers,
(allUsers, activeUsers, premiumUsers) => ({
total: allUsers.length,
active: activeUsers.length,
premium: premiumUsers.length,
activeRatio: activeUsers.length / allUsers.length,
premiumRatio: premiumUsers.length / allUsers.length
})
);import { selectUserSummary } from './user.selectors';
describe('User Selectors', () => {
it('should select user summary', () => {
const mockState = {
users: {
entities: [
{ id: '1', name: 'John', active: true },
{ id: '2', name: 'Jane', active: false }
]
}
};
const result = selectUserSummary(mockState);
expect(result).toEqual({
total: 2,
active: 1,
inactive: 1
});
});
it('should memoize results', () => {
const state1 = { users: { entities: [] } };
const state2 = { users: { entities: [] } };
const result1 = selectUserSummary(state1);
const result2 = selectUserSummary(state2);
// Same reference due to memoization
expect(result1).toBe(result2);
});
});createFeatureSelector for clear state structure