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:
- Create a simple Angular component. This component should have some observable subscription or a timer that needs to be cleaned up.
- Implement the
ngOnDestroymethod. Inside this method, you must ensure that any active subscriptions or running timers are properly unsubscribed or cleared. - Simulate component destruction. You'll need a mechanism to trigger the destruction of this component, allowing you to observe the
ngOnDestroymethod being called. - Log to the console. Use
console.logstatements to indicate when the component is initialized, when the observable/timer is active, and most importantly, whenngOnDestroyis 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
ngOnDestroymethod 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
ngOnDestroyis called, preventing potential memory leaks or unexpected behavior.
Edge Cases to Consider:
- What happens if
ngOnDestroyis 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
ngOnDestroyfor resource cleanup. - Use
console.logfor output. - Avoid using third-party libraries beyond standard RxJS if you choose the observable example.
Notes
- Remember that
ngOnDestroyis a lifecycle hook. You need to importOnDestroyfrom@angular/coreand implement theOnDestroyinterface 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
ngOnDestroyto prevent memory leaks.