The Dangers of Not Unsubscribing from Observables in Angular

Observables are a powerful tool for working with asynchronous data in Angular. When you subscribe to an Observable in Angular, the Observable starts emitting data and the subscription receives that data. However, if you don't unsubscribe from the subscription when you're done with it, the subscription will continue to exist in memory, and the Observable will continue to emit data even if you no longer need it you could end up with memory leaks and performance issues.

Memory leaks

If you don't unsubscribe from an Observable, the subscription and all its associated resources (e.g. memory, network connections) will remain in memory, even after the component that created it has been destroyed. Over time, this can cause a buildup of unused resources and lead to memory leaks, which can slow down your application and make it less stable.

Performance issues

If you don't unsubscribe from an Observable, the Observable will continue to emit data and the subscription will continue to receive that data, even if you no longer need it. This can lead to unnecessary processing and network traffic, which can slow down your application and affect its performance.

To avoid these issues, it's important to always unsubscribe from Observables when you're done with them. You can do this by storing a reference to the subscription and calling its `unsubscribe()` method in the `ngOnDestroy()` lifecycle hook of the component that created it. This ensures that the Observable is cleaned up properly when the component is destroyed, and its associated resources are released from memory.

Here's an example of how this can happen:

typescript
import { Component, OnInit } from '@angular/core';
import { interval } from 'rxjs';

@Component({
  selector: 'app-bad-example',
  template: '{{ value }}'
})
export class BadExampleComponent implements OnInit {
  value: number;

  ngOnInit() {
    interval(1000).subscribe(value => {
      this.value = value;
    });
  }
}

In this example, we're subscribing to an Observable returned by the `interval()` function and updating the `value` property of the component every second. However, we're never unsubscribing from this Observable, which means it will keep running indefinitely even after the component is destroyed.

To fix this, we can store a reference to the subscription and unsubscribe from it when the component is destroyed:

typescript
import { Component, OnInit, OnDestroy } from '@angular/core';
import { interval, Subscription } from 'rxjs';

@Component({
  selector: 'app-good-example',
  template: '{{ value }}'
})
export class GoodExampleComponent implements OnInit, OnDestroy {
  value: number;
  subscription: Subscription;

  ngOnInit() {
    this.subscription = interval(1000).subscribe(value => {
      this.value = value;
    });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Now, we're storing the subscription in a property of the component and unsubscribing from it in the `ngOnDestroy()` lifecycle hook. This ensures that the Observable will be cleaned up properly when the component is destroyed. This will be possible to rewrite to decrease the amount of boilerplate code with a new provider named `DestroyRef` in Angular version 16 - read the article about DestroyRef.

Simulating the memory leaks in Angular

Simulating memory leaks in an Angular application can be a bit tricky, but here's one approach you can take:

  1. Create a component with an Observable subscription: In your Angular application, create a component that subscribes to an Observable, but does not unsubscribe from it.
  2. Generate a large number of instances: Use a loop to generate a large number of instances of this component (e.g. 100 or more).
  3. Monitor memory usage: Use your browser's developer tools to monitor the memory usage of your application as you generate more instances of the component. You should see the memory usage increase as more components are created, but not decrease when they are destroyed.
  4. Force garbage collection: After generating a large number of instances of the component, force garbage collection in your browser's developer tools. You should see that the memory usage does not decrease, indicating that there is a memory leak.
  5. Fix the issue: To fix the memory leak, add a `ngOnDestroy()` method to your component and unsubscribe from the Observable in this method. Repeat the steps above to ensure that memory usage decreases after garbage collection.

Note that simulating memory leaks in this way can be resource-intensive, so you should only do it in a development or testing environment. Additionally, it's important to keep in mind that real-world memory leaks may not be as obvious as the ones you can simulate in this way, so you should also use other tools and techniques (such as profiling and memory analysis) to diagnose and fix memory leaks in your Angular application.

Summary

In this article, we've looked into what could happen if we're not unsubscribing from an Observable in an Angular application. It was proven that it can lead to memory leaks and performance issues, as the subscription and its associated resources (e.g. memory, network connections) will remain in memory even after the component that created it has been destroyed. To avoid these issues, it's important to always unsubscribe from Observables when you're done with them.

A video testing this code can be found below: