codejump

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

Waiting at the end of this page.

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:

typescript
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:

typescript
@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 drills

Reading this page is the cheap half. These are the exercises that make you use it.