NgRx selectors are not just state getters. They create memoized, composable read models that power derived state in Angular apps. Interviewers expect you to explain selector composition, NgRx selector memoization behavior, and why this improves performance.
NgRx selectors beyond getting state: memoization, derived state, and Angular performance
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core idea
Selectors are your app’s read layer. Instead of pushing filtering/sorting/mapping logic into components, you compose selectors once and expose a memoized view model. This keeps components thin and avoids repeated recalculation.
Selector level | Example | Responsibility |
|---|---|---|
Feature selector |
| Grab one feature slice from root state |
Entity/base selectors |
| Read normalized raw state |
Derived selectors |
| Transform/filter/sort domain data |
VM selector |
| Return final UI-ready shape for component/template |
import { createFeatureSelector, createSelector } from '@ngrx/store';
type Product = { id: string; name: string; price: number };
type SortKey = 'name-asc' | 'price-desc';
interface ProductsState {
entities: Record<string, Product>;
ids: string[];
query: string;
sort: SortKey;
loading: boolean;
error: string | null;
}
const selectProductsFeature = createFeatureSelector<ProductsState>('products');
const selectEntities = createSelector(selectProductsFeature, s => s.entities);
const selectIds = createSelector(selectProductsFeature, s => s.ids);
const selectQuery = createSelector(selectProductsFeature, s => s.query);
const selectSort = createSelector(selectProductsFeature, s => s.sort);
const selectLoading = createSelector(selectProductsFeature, s => s.loading);
const selectError = createSelector(selectProductsFeature, s => s.error);
const selectAllProducts = createSelector(selectIds, selectEntities, (ids, entities) =>
ids.map(id => entities[id])
);
const selectFilteredProducts = createSelector(selectAllProducts, selectQuery, (products, query) => {
const q = query.trim().toLowerCase();
if (!q) return products;
return products.filter(p => p.name.toLowerCase().includes(q));
});
const selectSortedProducts = createSelector(selectFilteredProducts, selectSort, (products, sort) => {
const copy = [...products];
if (sort === 'name-asc') return copy.sort((a, b) => a.name.localeCompare(b.name));
return copy.sort((a, b) => b.price - a.price);
});
export const selectProductsVm = createSelector(
selectSortedProducts,
selectLoading,
selectError,
(items, loading, error) => ({ items, loading, error, total: items.length })
);
Memoization behavior | What happens | Practical impact |
|---|---|---|
First evaluation | Projector runs and result is cached | Expected initial compute cost |
Same input selector references | Cached result returned, projector does not re-run | Avoids unnecessary CPU and re-renders |
Any input reference changes | Projector re-runs once and cache is updated | Recompute only when data actually changed |
Inputs recreated every time | Memoization is defeated | Performance degrades and UI churn increases |
// ❌ Pitfall: selecting whole state and rebuilding arrays/objects in component
@Component({ selector: 'app-products', template: `...` })
export class ProductsComponent {
// broad selection + local mapping each emission
readonly vm$ = this.store.select(state => state).pipe(
map(state => {
const items = Object.values(state.products.entities)
.filter(p => p.name.includes(state.products.query))
.sort((a, b) => a.name.localeCompare(b.name));
return {
items: [...items], // new object/array every time
total: items.length,
loading: state.products.loading
};
})
);
}
// ✅ Better: keep composition in selectors, component only selects VM
@Component({ selector: 'app-products', template: `...` })
export class ProductsComponent {
readonly vm$ = this.store.select(selectProductsVm);
}
Common pitfall | Why it hurts | Fix |
|---|---|---|
Selecting the whole root state in components | Any unrelated state change can trigger unnecessary recalculation | Select the narrowest feature/VM selector |
Doing filter/sort/map in components repeatedly | Logic duplication + unstable references + harder tests | Move derivation into composed selectors |
Returning brand-new objects everywhere without need | Memoization cannot help if inputs are constantly rebuilt | Preserve stable references where possible and compose selectors |
Keeping selectors too shallow (no VM selector) | Components become bloated orchestration layers | Create a single UI-focused VM selector |
Interview summary
NgRx selectors are a memoized read-model layer, not just property accessors. Compose selectors from feature state to entities to derived filtered/sorted data, then expose a final view model selector to components. This improves Angular selector performance, reduces component complexity, and keeps state logic reusable.