AcademyAngular
Lifecycle Hooks and Custom Directives
ngOnInit is where setup belongs, ngOnDestroy where it gets torn down; a custom attribute directive follows the same discipline for its own listeners.
2 min readUpdated 3 exercises
Setup belongs in ngOnInit, not the constructor
The constructor exists for dependency injection, not for starting timers, subscriptions or anything with a side effect — @Input() values are not guaranteed to be set yet when the constructor runs, so logic that depends on them belongs in ngOnInit, which fires once, right after Angular has set the component's inputs for the first time:
private intervalId?: ReturnType<typeof setInterval>;
ngOnInit(): void {
this.intervalId = setInterval(() => this.tick(), 1000);
}
ngOnDestroy(): void {
clearInterval(this.intervalId);
}A timer that "starts too early" is frequently one moved into the constructor, ticking against @Input() state that has not arrived yet. The more damaging half of the same bug is the missing ngOnDestroy: without it, that setInterval keeps firing after the component is destroyed and removed from the DOM, updating fields nothing reads anymore and holding the component instance itself alive in memory — a leak that is invisible until the page has been open long enough, or the component has been created and destroyed enough times, for it to show up as one.
@HostListener wires a directive's own event handling
A custom attribute directive attaches behavior to whatever element hosts it, and @HostListener is how it listens for events without a template of its own:
@Directive({ selector: '[clickOutside]' })
export class ClickOutsideDirective {
@Output() clickOutside = new EventEmitter<void>();
@HostListener('document:click', ['$event'])
onDocumentClick(event: MouseEvent): void {
if (!this.elementRef.nativeElement.contains(event.target)) {
this.clickOutside.emit();
}
}
}The document:click target — not the plain click — is what makes this pattern work at all: listening on the element itself would never catch a click that happened outside it. A menu that closes even when you click inside it is the same directive with that containment check missing or inverted — .contains() returning true (a click inside) treated the same as false (a click outside), so every click, regardless of where, is read as "outside."
What interviewers check
Whether ngOnDestroy is something you reach for automatically alongside anything ngOnInit starts — a timer, a subscription, a listener — and whether you can explain why a click-outside directive has to listen on document, not on its own host element, to detect a click that happened anywhere else.
Now practice it
1 quiz · 2 drillsReading this page is the cheap half. These are the exercises that make you use it.
- ngOnInit & ngOnDestroyProFix a timer that starts too early in the constructor and, worse, never stops when the component is destroyed.DrillSolve · MediumDrillSolve · Medium
- A Custom Attribute DirectiveProFix a custom directive that listens in the wrong place and closes a menu even when the click happened inside it.DrillSolve · MediumDrillSolve · Medium
- RxJS & Observables · Observables & HTTPObservable basics that pair naturally with the subscription-cleanup discipline ngOnDestroy exists for.QuizTake · EasyQuizTake · Easy
Read next
Attribute Fallthrough and ngOnChanges
A class or attribute a caller puts on a component's host element falls through by default; @HostBinding sets one from inside. ngOnChanges reports what changed.
2 min
Services and Async State with Signals
A service is Angular's unit of shared, reusable logic — its composable — and signal() is how it holds async state a component can read reactively.
2 min