Hone logo
Hone
Problems

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:

  1. All products: A selector that returns the entire array of products from the store.
  2. Products by category: A selector that accepts a category name as an argument and returns only the products belonging to that category.
  3. Product by ID: A selector that accepts a product ID as an argument and returns the specific product with that ID, or undefined if not found.
  4. 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 createSelector from @ngrx/store to 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 products property, 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 products array 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' },
      ]
      
  • selectProductsByCategory('Electronics'):
    • Output:
      [
        { id: 1, name: 'Laptop', price: 1200, category: 'Electronics' },
        { id: 3, name: 'Mouse', price: 50, category: 'Electronics' },
      ]
      
  • selectProductById(3):
    • Output:
      { id: 3, name: 'Mouse', price: 50, category: 'Electronics' }
      
  • selectTotalPrice:
    • Output: 1335 (1200 + 25 + 50 + 60)

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: []
  • selectProductsByCategory('Apparel'):
    • Output: []
  • selectProductById(10):
    • Output: undefined
  • selectTotalPrice:
    • Output: 0

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/store library, specifically createSelector.
  • 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 createFeatureSelector if your products slice is part of a larger feature in your NgRx store. However, for this challenge, assume products is directly accessible from the root state or a primary feature.
  • Think about how createSelector uses result equality to determine if a recalculation is needed.
  • For selectProductById, the find() array method is a good candidate.
  • For selectTotalPrice, the reduce() array method is a suitable choice.
Loading editor...
typescript