Reacting to Input Changes in Angular with ngOnChanges
Angular components often need to respond dynamically to changes in their input properties. The ngOnChanges lifecycle hook provides a robust mechanism for this, allowing you to execute logic whenever one or more of your component's @Input() properties change. This challenge will test your understanding of how to correctly implement and utilize ngOnChanges to manage component state based on external data.
Problem Description
You are tasked with creating an Angular component called ProductDisplayComponent. This component will receive a product object as an @Input() property. The product object will have at least two properties: name (string) and price (number).
Your goal is to implement the ngOnChanges lifecycle hook within ProductDisplayComponent to:
- Log changes: Whenever the
productinput property changes, log a message to the console indicating which property changed and its new value. - Update internal state (if necessary): If the
priceproperty changes, update an internal component property calleddiscountedPriceto be 90% of the new price. If theproductobject itself is reassigned (e.g., a completely new product is passed), resetdiscountedPriceto the product's current price. - Initial display: Ensure that when the component first loads with a product, the
discountedPriceis correctly initialized.
Key Requirements:
- Implement the
OnChangesinterface. - Correctly use the
ngOnChangesmethod. - Access the
SimpleChangesobject passed tongOnChanges. - Distinguish between changes to individual properties within the
productobject and the reassignment of the entireproductobject. - The component should have an
@Input()property namedproductof type{ name: string; price: number; }. - The component should have an internal property
discountedPriceof typenumber.
Expected Behavior:
- When the
productinput is initially set,ngOnChangeswill be called, anddiscountedPricewill be initialized. - If only the
priceof an existingproductobject changes (e.g.,componentInstance.product.price = newPrice),ngOnChangeswill be called, anddiscountedPricewill be updated to 90% of the new price. - If the entire
productobject is replaced (e.g.,componentInstance.product = newProductObject),ngOnChangeswill be called, anddiscountedPricewill be reset to the price of the new product. - The console logs should clearly indicate what changed.
Edge Cases:
- What happens if the
productinput isnullorundefinedinitially? The component should gracefully handle this without errors. - What happens if the
priceproperty of theproductobject is not a valid number? (For this challenge, assume valid number inputs for price, but be aware of this in real-world scenarios).
Examples
Let's assume we have a parent component that passes data to ProductDisplayComponent.
Example 1: Initial Load and Price Change
-
Parent Component Action:
- Sets
productInput = { name: 'Laptop', price: 1200 }. - Later, changes
productInput = { name: 'Laptop', price: 1100 }(only price changed).
- Sets
-
ProductDisplayComponentBehavior:- Initial call to
ngOnChanges:Console Output: ProductDisplayComponent: ngOnChanges called. Console Output: ProductDisplayComponent: Initial product set: {"name":"Laptop","price":1200}discountedPriceis initialized to1200. - Second call to
ngOnChanges(after price change):Console Output: ProductDisplayComponent: ngOnChanges called. Console Output: ProductDisplayComponent: product changed. Previous: {"name":"Laptop","price":1200}, Current: {"name":"Laptop","price":1100} Console Output: ProductDisplayComponent: Price changed. Old price: 1200, New price: 1100discountedPriceis updated to1100 * 0.9 = 990.
- Initial call to
Example 2: Product Reassignment
-
Parent Component Action:
- Sets
productInput = { name: 'Keyboard', price: 75 }. - Later, changes
productInput = { name: 'Mouse', price: 25 }(entire product object replaced).
- Sets
-
ProductDisplayComponentBehavior:- Initial call to
ngOnChanges:Console Output: ProductDisplayComponent: ngOnChanges called. Console Output: ProductDisplayComponent: Initial product set: {"name":"Keyboard","price":75}discountedPriceis initialized to75. - Second call to
ngOnChanges(after product reassignment):Console Output: ProductDisplayComponent: ngOnChanges called. Console Output: ProductDisplayComponent: product changed. Previous: {"name":"Keyboard","price":75}, Current: {"name":"Mouse","price":25} Console Output: ProductDisplayComponent: Product object reassigned.discountedPriceis reset to25.
- Initial call to
Example 3: Initial null product, then valid product
-
Parent Component Action:
- Sets
productInput = null. - Later, sets
productInput = { name: 'Monitor', price: 300 }.
- Sets
-
ProductDisplayComponentBehavior:- First call to
ngOnChanges:Console Output: ProductDisplayComponent: ngOnChanges called. Console Output: ProductDisplayComponent: Initial product set: nulldiscountedPriceremains undefined or0(depending on initial declaration). - Second call to
ngOnChanges:Console Output: ProductDisplayComponent: ngOnChanges called. Console Output: ProductDisplayComponent: product changed. Previous: null, Current: {"name":"Monitor","price":300} Console Output: ProductDisplayComponent: Initial product set: {"name":"Monitor","price":300}discountedPriceis initialized to300.
- First call to
Constraints
- The
ProductDisplayComponentmust be a standard Angular component. - The
productinput property must be decorated with@Input(). - The
ngOnChangesmethod signature must be correct. - The
SimpleChangesobject provided tongOnChangesmust be utilized to determine property changes. - Performance is not a critical constraint for this challenge, but avoid overly inefficient operations.
Notes
- The
SimpleChangesobject is a key-value pair where keys are the names of input properties that changed, and values areSimpleChangeobjects. - A
SimpleChangeobject has properties:previousValue,currentValue, andfirstChange(boolean). - When an
@Input()property is changed,ngOnChangesis called beforengOnInit(if it's the first change) and beforengDoCheck. - Consider how you will differentiate between a change to a property within the
productobject versus theproductobject itself being replaced. ThecurrentValueandpreviousValueinSimpleChangescan help here. Ifproductis an object, thepreviousValueandcurrentValuemight be the same object reference if only a nested property changed, but different references if the object itself was reassigned. A more robust way to detect internal changes is to check ifchanges['product'].currentValueandchanges['product'].previousValueare different instances or ifchanges['product'].firstChangeis true. - The
productinput can benullorundefined. Ensure your component handles this scenario gracefully. - For logging, use
console.log().