Angular Feature State Management Challenge
This challenge focuses on implementing robust feature state management within an Angular application. Effectively managing the state of specific features is crucial for building scalable and maintainable Angular applications, especially as they grow in complexity. You will demonstrate your ability to organize and access data related to a particular feature in a clean and decoupled manner.
Problem Description
Your task is to create a feature state management solution for a hypothetical "Product Catalog" feature in an Angular application. This state should encapsulate all information related to products, such as a list of products, the currently selected product, and loading/error states associated with fetching product data.
Key Requirements:
- Feature Store: Create a dedicated "store" for the Product Catalog feature. This store should hold the state for products.
- State Properties: The Product Catalog state should include:
products: An array of product objects (you can define a simpleProductinterface for this).selectedProductId: The ID of the currently selected product (ornullif none is selected).isLoading: A boolean indicating if product data is currently being fetched.error: A string ornullrepresenting any error message encountered during data fetching.
- Selectors: Implement selectors to retrieve specific pieces of state from the Product Catalog store. Examples include:
selectAllProducts: Returns the entireproductsarray.selectIsLoading: Returns theisLoadingboolean.selectError: Returns theerrorstring ornull.selectSelectedProduct: Returns the full product object for theselectedProductId, ornullif no product is selected or found.
- Actions/Mutations (Conceptual): While you won't implement a full Redux-like action system, conceptually demonstrate how you would trigger state changes. This can be done by creating simple methods within your state management service to update the state. For example:
loadProducts(): SetsisLoadingto true and simulates fetching products.setProducts(products: Product[]): Updates theproductsarray and setsisLoadingto false.setProductLoadingError(error: string): Sets theerrorandisLoadingto false.selectProduct(productId: number): UpdatesselectedProductId.
- Service Integration: Create an Angular service that utilizes this feature state. This service should expose methods to interact with the state (e.g., through the selectors) and trigger state mutations.
Expected Behavior:
- When
loadProducts()is called,isLoadingshould becometrue. - When
setProducts()is called with a list of products, theproductsstate should be updated, andisLoadingshould becomefalse. - When
setProductLoadingError()is called, theerrorstate should be updated, andisLoadingshould becomefalse. selectAllProductsshould always return the current array of products.selectSelectedProductshould correctly return the product corresponding toselectedProductIdornull.
Edge Cases to Consider:
- What happens if
selectSelectedProductis called when no product is selected? - What happens if
selectSelectedProductis called with an ID that doesn't exist in theproductsarray? - How does the state handle concurrent loading attempts (though for this challenge, simple sequential updates are sufficient).
Examples
Example 1: Initial State and Loading
Input:
- Call `productCatalogStateService.loadProducts()`
Output (conceptual state changes):
{
products: [],
selectedProductId: null,
isLoading: true,
error: null
}
Explanation:
Initially, the state is empty. Calling `loadProducts` sets `isLoading` to true to indicate that data fetching is in progress.
Example 2: Data Successfully Loaded
Input:
- Call `productCatalogStateService.loadProducts()`
- Assume the actual product data fetched is:
[
{ id: 1, name: 'Laptop', price: 1200 },
{ id: 2, name: 'Keyboard', price: 75 }
]
- Call `productCatalogStateService.setProducts(fetchedProducts)`
Output (conceptual state changes):
{
products: [
{ id: 1, name: 'Laptop', price: 1200 },
{ id: 2, name: 'Keyboard', price: 75 }
],
selectedProductId: null,
isLoading: false,
error: null
}
Explanation:
After the products are fetched and set, the `products` array is populated, and `isLoading` is reset to false.
Example 3: Selecting a Product
Input:
- State as in Example 2
- Call `productCatalogStateService.selectProduct(1)`
Output (conceptual state changes):
{
products: [
{ id: 1, name: 'Laptop', price: 1200 },
{ id: 2, name: 'Keyboard', price: 75 }
],
selectedProductId: 1,
isLoading: false,
error: null
}
Using `productCatalogStateService.selectSelectedProduct()` would return:
{ id: 1, name: 'Laptop', price: 1200 }
Explanation:
The `selectedProductId` is updated to 1. The `selectSelectedProduct` selector now returns the full product object for ID 1.
Example 4: Error Handling
Input:
- Call `productCatalogStateService.loadProducts()`
- Assume an error occurs during fetch, and the error message is "Network Error"
- Call `productCatalogStateService.setProductLoadingError("Network Error")`
Output (conceptual state changes):
{
products: [], // Or previous products if not reset on error
selectedProductId: null,
isLoading: false,
error: "Network Error"
}
Explanation:
The `error` state is populated, and `isLoading` is set to false, indicating the loading process has completed with an error.
Constraints
- You are expected to use TypeScript for all implementations.
- The solution should be designed to be testable.
- Avoid using external state management libraries like NgRx or Akita. Implement the core logic yourself using Angular services and RxJS subjects/behavior subjects for state observation.
- Focus on clarity and maintainability of the state management pattern.
Notes
- Consider using
BehaviorSubjectfrom RxJS to manage your state, as it allows components to subscribe and immediately receive the current state. - Think about how components would subscribe to these state changes and react to them.
- The
Productinterface can be as simple as{ id: number; name: string; price: number; }. - The "service integration" part means creating a service that uses your state management logic, not necessarily building a full feature service from scratch. The focus is on how that service interacts with and exposes the feature state.