Angular calls a fixed set of methods on your component as it is created, updated and destroyed.
The list is worth knowing. What is more useful is knowing how few of them modern Angular still needs
— the pizza app has one ngOnInit across thirty components, and no other
lifecycle hook at all.
The hooks, in order
ngOnChanges — an input changed. Receives a SimpleChanges bag, fires
for every input at once, and runs before ngOnInit.
ngOnInit — once, after the first inputs are set.
ngDoCheck — every change-detection pass. Almost never what you want.
ngAfterContentInit / ngAfterContentChecked — projected content is
ready.
ngAfterViewInit / ngAfterViewChecked — the component's own template
and children are in the DOM.
ngOnDestroy — being torn down. Historically where every unsubscribe went.
Each is a method you declare; implementing the matching interface —
implements OnInit — is optional and worth doing, because a typo in the method name is
otherwise silent.
The one that survives: ngOnInit
A component's constructor runs before its inputs are set. So anything that depends on an input cannot go there:
ngOnInit(): void {
void this.poll();
}⚠️ This is not a style preference. Reading a required input from the constructor throws
NG0950 at runtime, with nothing failing at build time. The input is assigned
after construction, and the constructor is simply too early.
There is no React equivalent to this gap, because there is none to have: props are an argument to the function, so they exist before the body runs.
What replaced the rest
ngOnChanges is now computed
readonly isPizza = computed(() => this.product().type === 'PIZZA');
readonly cheapest = computed(() => Math.min(...this.product().sizes.map((s) => s.price)));An input() is a signal, so anything derived from it can be a
computed, which recalculates when that input changes and not when any other
does. ngOnChanges fires for all of them and hands you a bag to unpick — strictly worse
for the job it was built for.
ngAfterViewInit is now afterNextRender
afterNextRender(() => {
if (this.appAutofocus()) this.host.nativeElement.focus();
});afterNextRender runs once after the DOM is written;
afterRenderEffect is its reactive sibling, re-running when a signal it read changes —
which is what the modal uses to re-focus its close button each time it reopens.
Both are also SSR-safe: they do not run on the server, where there is no DOM.
ngAfterViewInit does, which is a common source of "ResizeObserver is not
defined" during server rendering.
ngOnDestroy is now DestroyRef
const destroyRef = inject(DestroyRef);
afterNextRender(() => {
const observer = new ResizeObserver(([entry]) => {
const next = Math.round(entry.contentRect.width);
if (next > 0) width.set(next);
});
observer.observe(host.nativeElement);
destroyRef.onDestroy(() => observer.disconnect());
});This is the real gain, and it is not about syntax. DestroyRef can be injected
anywhere there is an injection context — including inside a plain function like this
one, which is not a component and has no lifecycle of its own to hook.
That is what lets the whole behaviour be packaged as a function a component calls, with its
setup and its teardown sitting next to each other. With ngOnDestroy, the
observer would have to be created in one method and disconnected in another, on a component that
does not otherwise care, with a field between them holding the reference. Every leak in that pattern
comes from the distance between those two lines.
For observables, the same idea has a dedicated operator:
source$.pipe(takeUntilDestroyed()).subscribe(handler);It unsubscribes when the injection context is destroyed, replacing the
destroy$ = new Subject() plus takeUntil(this.destroy$) plus
ngOnDestroy ritual that used to appear in every second Angular component.
Effects have their own lifetime
An effect created in an injection context is destroyed with its owner, so most
effects need no teardown at all. Where the handle is kept, the cleanup is explicit:
this.destroyRef.onDestroy(() => ref.destroy());The summary worth remembering
Reach for ngOnInit when you need an input at startup. Reach for
afterNextRender when you need the DOM. Reach for DestroyRef to clean up.
Everything else the hooks used to do is better done by computed and
effect, which is why a modern Angular component usually has none of them.
What is next
Services and dependency injection — the inject() that has been quietly doing the
work in every file so far.