Angular NgRx Selectors: Efficiently Querying State
This challenge focuses on creating and utilizing NgRx selectors in Angular applications. Selectors are a powerful tool for extracting specific pieces of state from your NgRx store in a performant and memoized way. Mastering selectors is crucial for building maintainable and scalable Angular applications that leverage NgRx for state management.
Problem Description
You are tasked with creating a set of NgRx selectors for a hypothetical e-commerce application. The application manages a list of products, each with properties like id, name, price, and category. You will need to implement selectors to retrieve:
- All products: A selector that returns the entire array of products from the store.
- Products by category: A selector that accepts a category name as an argument and returns only the products belonging to that category.
- Product by ID: A selector that accepts a product ID as an argument and returns the specific product with that ID, or
undefinedif not found. - Total price of all products: A selector that calculates and returns the sum of the prices of all products currently in the store.
Key Requirements:
- Use
createSelectorfrom@ngrx/storeto define all your selectors. - Ensure selectors are memoized to avoid unnecessary recalculations.
- The "Products by category" and "Product by ID" selectors should be factory selectors, meaning they take arguments.
- The state shape will be an object containing a
productsproperty, which is an array of product objects.
Expected Behavior:
- Calling the "All products" selector should always return the current array of products.
- Calling the "Products by category" selector with a valid category should return an array of matching products. If no products match the category, it should return an empty array.
- Calling the "Product by ID" selector with a valid ID should return the corresponding product object. If the ID is not found, it should return
undefined. - Calling the "Total price of all products" selector should accurately sum the prices of all products.
Edge Cases:
- What happens if the
productsarray is empty when calculating the total price? - What happens when searching for a category or ID that does not exist?
Examples
Example 1: Basic Product Retrieval
Input State:
interface AppState {
products: Product[];
}
interface Product {
id: number;
name: string;
price: number;
category: string;
}
const initialState: AppState = {
products: [
{ id: 1, name: 'Laptop', price: 1200, category: 'Electronics' },
{ id: 2, name: 'T-Shirt', price: 25, category: 'Apparel' },
{ id: 3, name: 'Mouse', price: 50, category: 'Electronics' },
{ id: 4, name: 'Jeans', price: 60, category: 'Apparel' },
],
};
Selectors and Outputs:
selectAllProducts:- Output:
[ { id: 1, name: 'Laptop', price: 1200, category: 'Electronics' }, { id: 2, name: 'T-Shirt', price: 25, category: 'Apparel' }, { id: 3, name: 'Mouse', price: 50, category: 'Electronics' }, { id: 4, name: 'Jeans', price: 60, category: 'Apparel' }, ]
- Output:
selectProductsByCategory('Electronics'):- Output:
[ { id: 1, name: 'Laptop', price: 1200, category: 'Electronics' }, { id: 3, name: 'Mouse', price: 50, category: 'Electronics' }, ]
- Output:
selectProductById(3):- Output:
{ id: 3, name: 'Mouse', price: 50, category: 'Electronics' }
- Output:
selectTotalPrice:- Output:
1335(1200 + 25 + 50 + 60)
- Output:
Explanation: These selectors demonstrate the basic functionality of retrieving all products, filtering by a specific attribute, finding a single item by its identifier, and performing an aggregation.
Example 2: Edge Cases
Input State:
interface AppState {
products: Product[];
}
interface Product {
id: number;
name: string;
price: number;
category: string;
}
const emptyState: AppState = {
products: [],
};
Selectors and Outputs (with emptyState):
selectAllProducts:- Output:
[]
- Output:
selectProductsByCategory('Apparel'):- Output:
[]
- Output:
selectProductById(10):- Output:
undefined
- Output:
selectTotalPrice:- Output:
0
- Output:
Explanation: This example covers scenarios with an empty product list, searching for non-existent categories or IDs, and shows that the total price correctly defaults to 0 when there are no products.
Constraints
- You must use the NgRx
@ngrx/storelibrary, specificallycreateSelector. - The state shape will always be
{ products: Product[] }. - Product IDs are unique numbers.
- Product prices are non-negative numbers.
- Category names are strings.
- Your selectors should be efficient and leverage memoization.
Notes
- Remember that factory selectors need to return a function that takes the selector's arguments and then applies the memoized logic.
- Consider using
createFeatureSelectorif yourproductsslice is part of a larger feature in your NgRx store. However, for this challenge, assumeproductsis directly accessible from the root state or a primary feature. - Think about how
createSelectoruses result equality to determine if a recalculation is needed. - For
selectProductById, thefind()array method is a good candidate. - For
selectTotalPrice, thereduce()array method is a suitable choice.