Lifecycle Hooks
React's useEffect (Fundamentals Chapter 8) collapsed "run on mount," "run on update," and "clean up on unmount" into one hook differentiated by its dependency array. Angular splits these into separate, explicitly-named lifecycle hook methods — each a method with a fixed name that Angular calls at a specific moment. A component opts into one by implementing the matching interface and writing the method.
ngOnInit — Setup After Creation
ngOnInit runs once, right after Angular has created the component and set its initial @Input values — the standard place for setup work like an initial data fetch (used already in Chapter 11). It's the direct equivalent of React's useEffect(() => {...}, []) with an empty dependency array. Implementing OnInit isn't strictly required for the method to run, but doing so lets TypeScript catch a misspelled ngOnInit — strongly recommended.
Why Not the Constructor?
A natural question: why not just do setup in the class constructor? The constructor runs when the object is first created, before Angular has finished wiring it up — specifically before @Input properties have their values. Putting data fetching or any logic depending on inputs in ngOnInit guarantees those inputs are ready. The convention: the constructor is for dependency injection only (or just use inject()); ngOnInit is for actual initialization logic.
ngOnChanges — Responding to Input Changes
ngOnChanges runs whenever an @Input value changes (and once initially, before ngOnInit) — the rough equivalent of useEffect with a specific prop in its dependency array. The SimpleChanges argument is an object keyed by which inputs changed, each entry holding previousValue and currentValue — useful when a component needs to react to which input changed and by how much, like recomputing a chart only when its data prop actually changes.
ngOnDestroy — Cleanup Before Removal
ngOnDestroy runs just before Angular removes the component — the place for cleanup, exactly mirroring the cleanup function returned from a React useEffect. This is where the manual subscriptions flagged in Chapter 11's warning get unsubscribed, timers cleared, and listeners removed. The same rule from React applies: anything that "starts" something ongoing needs a matching "stop" here, or it leaks.
Subscription and unsubscribing in ngOnDestroy is verbose. Two modern alternatives largely remove the need: the async pipe (Chapter 11) cleans up its own subscription automatically, and the takeUntilDestroyed() operator ties an Observable's lifetime to the component's automatically. Prefer those where possible; reach for an explicit ngOnDestroy for non-RxJS cleanup (a manual timer, a third-party library handle).
ngOnChanges reacts to changes in @Input properties specifically, not to internal state changes. And like React's dependency comparison, it detects a changed input by reference for objects/arrays — mutating an object passed as an input in place (rather than passing a new one) won't be seen as a change, the same immutability concern from React's Fundamentals Chapter 3. Pass a new object/array to trigger ngOnChanges reliably.
| Angular hook | React useEffect equivalent |
|---|---|
| ngOnInit | useEffect(() => {...}, []) (mount) |
| ngOnChanges | useEffect(() => {...}, [someProp]) |
| ngOnDestroy | The cleanup function returned from useEffect |
Coding Challenges
Build a component implementing OnInit that logs a message and fetches/sets some initial data in ngOnInit, confirming (via console) that it runs once after the component is created.
📄 View solutionBuild a child component with an @Input value, implementing OnChanges to log the previousValue and currentValue each time the input changes. Drive it from a parent with a button that changes the input.
📄 View solutionBuild a component that starts an RxJS interval subscription in ngOnInit (logging a tick each second) and properly unsubscribes in ngOnDestroy. Toggle the component's presence with @if in a parent to confirm the ticking stops when it's removed.
📄 View solutionChapter 12 Quick Reference
- ngOnInit — runs once after creation; the place for initial setup/data fetching (mount equivalent)
- Use
ngOnInit, not the constructor, for init logic — inputs aren't ready in the constructor - ngOnChanges(changes) — runs when an
@Inputchanges;SimpleChangesgives previous/current values - ngOnDestroy — runs before removal; the place for cleanup (unsubscribe, clear timers)
- Implement the matching interface (
OnInit/OnChanges/OnDestroy) for type safety - The
asyncpipe andtakeUntilDestroyed()reduce the need for manualngOnDestroycleanup ngOnChangesdetects object/array input changes by reference — pass new ones, don't mutate- Next chapter: standalone components and signals — Angular's modern direction