Hone logo
Hone
Problems

Angular Component Lifecycle: Mastering ngOnDestroy

In Angular, components have a lifecycle that dictates when certain methods are called. Understanding these lifecycle hooks is crucial for managing resources, preventing memory leaks, and ensuring your application behaves as expected. The ngOnDestroy hook is particularly important for cleaning up before a component is removed from the DOM. This challenge will test your ability to correctly implement and utilize ngOnDestroy.

Problem Description

Your task is to create an Angular component that demonstrates the proper implementation and behavior of the ngOnDestroy lifecycle hook. You will need to:

  1. Create a simple Angular component. This component should have some observable subscription or a timer that needs to be cleaned up.
  2. Implement the ngOnDestroy method. Inside this method, you must ensure that any active subscriptions or running timers are properly unsubscribed or cleared.
  3. Simulate component destruction. You'll need a mechanism to trigger the destruction of this component, allowing you to observe the ngOnDestroy method being called.
  4. Log to the console. Use console.log statements to indicate when the component is initialized, when the observable/timer is active, and most importantly, when ngOnDestroy is executed.

Expected Behavior:

  • When the component is initialized, a console log message should appear.
  • An observable subscription or timer should be started, with a console log message indicating its activity.
  • When the component is destroyed, the ngOnDestroy method should be called, and a specific console log message should appear, confirming that the cleanup has occurred.
  • The active subscription/timer should cease to function after ngOnDestroy is called, preventing potential memory leaks or unexpected behavior.

Edge Cases to Consider:

  • What happens if ngOnDestroy is called before the subscription/timer has even started (though less likely in a typical component lifecycle)?
  • How do you handle multiple subscriptions or timers within the same component?

Examples

Example 1: Using setTimeout

Scenario: A component that starts a setTimeout for 5 seconds upon initialization and logs a message. The ngOnDestroy should clear this timer.

// Component Template (app.component.html)
<div>
  <h1>{{ title }}</h1>
  <app-timer-component></app-timer-component>
</div>

// TimerComponent (timer.component.ts)
import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-timer-component',
  template: '<p>Timer Component is running...</p>'
})
export class TimerComponent implements OnInit, OnDestroy {
  private timerId: any;

  ngOnInit(): void {
    console.log('TimerComponent initialized.');
    this.timerId = setTimeout(() => {
      console.log('This message should NOT appear if ngOnDestroy works correctly.');
    }, 5000);
    console.log('Timer started.');
  }

  ngOnDestroy(): void {
    console.log('TimerComponent ngOnDestroy called.');
    if (this.timerId) {
      clearTimeout(this.timerId);
      console.log('Timer cleared.');
    }
  }
}

// AppComponent (app.component.ts) - responsible for conditionally rendering TimerComponent
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>Angular ngOnDestroy Challenge</h1>
    <button (click)="showTimer = !showTimer">
      {{ showTimer ? 'Hide Timer' : 'Show Timer' }}
    </button>
    <app-timer-component *ngIf="showTimer"></app-timer-component>
  `
})
export class AppComponent {
  title = 'ngOnDestroy Demo';
  showTimer = true;
}

Expected Console Output (when "Hide Timer" is clicked):

TimerComponent initialized.
Timer started.
TimerComponent ngOnDestroy called.
Timer cleared.

Explanation:

When the AppComponent is initialized, TimerComponent is rendered, ngOnInit is called, and the timer starts. Clicking the "Hide Timer" button sets showTimer to false, which causes Angular to destroy TimerComponent. This triggers ngOnDestroy, where the clearTimeout function is executed, preventing the 5-second timeout callback from running.

Example 2: Using RxJS Observables

Scenario: A component that subscribes to a simple RxJS interval observable upon initialization. ngOnDestroy should unsubscribe from this observable.

// Component Template (app.component.html)
<div>
  <h1>{{ title }}</h1>
  <app-observable-component></app-observable-component>
</div>

// ObservableComponent (observable.component.ts)
import { Component, OnInit, OnDestroy } from '@angular/core';
import { interval, Subscription } from 'rxjs';
import { take } from 'rxjs/operators';

@Component({
  selector: 'app-observable-component',
  template: '<p>Observable Component is running...</p>'
})
export class ObservableComponent implements OnInit, OnDestroy {
  private intervalSubscription: Subscription | undefined;

  ngOnInit(): void {
    console.log('ObservableComponent initialized.');
    this.intervalSubscription = interval(1000)
      .pipe(take(10)) // Limit to 10 emissions for demonstration
      .subscribe(value => {
        console.log(`Observable emitted: ${value}`);
      });
    console.log('Interval observable subscribed.');
  }

  ngOnDestroy(): void {
    console.log('ObservableComponent ngOnDestroy called.');
    if (this.intervalSubscription) {
      this.intervalSubscription.unsubscribe();
      console.log('Unsubscribed from interval observable.');
    }
  }
}

// AppComponent (app.component.ts) - same as Example 1, but renders ObservableComponent
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>Angular ngOnDestroy Challenge</h1>
    <button (click)="showObservable = !showObservable">
      {{ showObservable ? 'Hide Observable' : 'Show Observable' }}
    </button>
    <app-observable-component *ngIf="showObservable"></app-observable-component>
  `
})
export class AppComponent {
  title = 'ngOnDestroy Demo';
  showObservable = true;
}

Expected Console Output (when "Hide Observable" is clicked, assuming it runs for at least 1 second):

ObservableComponent initialized.
Interval observable subscribed.
Observable emitted: 0
Observable emitted: 1
... (emissions continue until the component is destroyed)
ObservableComponent ngOnDestroy called.
Unsubscribed from interval observable.

Explanation:

Similar to the timer example, ObservableComponent subscribes to an interval observable in ngOnInit. Clicking the "Hide Observable" button in AppComponent triggers the destruction of ObservableComponent. The ngOnDestroy method is then called, and this.intervalSubscription.unsubscribe() is executed, stopping further emissions from the observable and freeing up resources.

Constraints

  • Your Angular application should be set up using the Angular CLI.
  • The solution must be written in TypeScript.
  • You must demonstrate the use of ngOnDestroy for resource cleanup.
  • Use console.log for output.
  • Avoid using third-party libraries beyond standard RxJS if you choose the observable example.

Notes

  • Remember that ngOnDestroy is a lifecycle hook. You need to import OnDestroy from @angular/core and implement the OnDestroy interface for your component.
  • Think about how you'll manage multiple subscriptions. A common pattern is to store subscriptions in an array and then iterate through them in ngOnDestroy.
  • Consider what types of resources might need cleaning up in a real-world Angular application (e.g., event listeners, WebSocket connections, third-party library instances).
  • The goal is to show you understand how and why to use ngOnDestroy to prevent memory leaks.
Loading editor...
typescript