Skip to main content
A Subscription represents a disposable resource, typically the execution of an Observable. It provides the mechanism to cancel an Observable execution and free up resources.

What is a Subscription?

When you call subscribe() on an Observable, you get back a Subscription object. This object represents the ongoing execution and allows you to cancel it by calling unsubscribe().
Think of a Subscription as a receipt for your Observable execution - you can use it to cancel the execution and clean up resources.

Type Signature

interface Subscription extends SubscriptionLike {
  unsubscribe(): void;
  add(teardown: TeardownLogic): void;
  remove(subscription: Subscription): void;
  readonly closed: boolean;
}

interface SubscriptionLike extends Unsubscribable {
  unsubscribe(): void;
  readonly closed: boolean;
}

type TeardownLogic = Subscription | Unsubscribable | (() => void) | void;

Creating and Managing Subscriptions

Basic Subscription

import { interval } from 'rxjs';

const observable = interval(1000);
const subscription = observable.subscribe(x => console.log(x));

// Later: cancel the ongoing Observable execution
subscription.unsubscribe();
Failing to unsubscribe from long-lived Observables can cause memory leaks in your application.

Checking Subscription Status

import { interval } from 'rxjs';
import { take } from 'rxjs/operators';

const subscription = interval(1000)
  .pipe(take(3))
  .subscribe(x => console.log(x));

console.log('Closed?', subscription.closed); // false

setTimeout(() => {
  console.log('Closed?', subscription.closed); // true (after completion)
}, 4000);

Unsubscribing

Manual Unsubscription

import { interval } from 'rxjs';

const subscription = interval(1000).subscribe({
  next: x => console.log('Value:', x)
});

// Stop after 5 seconds
setTimeout(() => {
  subscription.unsubscribe();
  console.log('Unsubscribed');
}, 5000);

Automatic Unsubscription

import { of } from 'rxjs';

const subscription = of(1, 2, 3).subscribe({
  next: x => console.log(x),
  complete: () => console.log('Complete - auto unsubscribed')
});

// No need to manually unsubscribe
// Subscription is automatically closed after completion
console.log('Closed?', subscription.closed); // true

Combining Subscriptions

Adding Child Subscriptions

You can combine multiple subscriptions so that calling unsubscribe() on one will unsubscribe all of them:
import { interval } from 'rxjs';

const observable1 = interval(400);
const observable2 = interval(300);

const subscription = observable1.subscribe(x => console.log('first: ' + x));
const childSubscription = observable2.subscribe(x => console.log('second: ' + x));

subscription.add(childSubscription);

setTimeout(() => {
  // Unsubscribes BOTH subscription and childSubscription
  subscription.unsubscribe();
}, 1000);
This pattern is useful for managing multiple related subscriptions together, especially in components.

Removing Child Subscriptions

import { interval } from 'rxjs';

const subscription = interval(1000).subscribe(x => console.log('parent:', x));
const child = interval(500).subscribe(x => console.log('child:', x));

subscription.add(child);

// Remove the child subscription
subscription.remove(child);

// Now unsubscribing parent won't affect child
subscription.unsubscribe();
child.unsubscribe(); // Must manually unsubscribe child

Practical Examples

Component Lifecycle Management

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

class DataComponent implements OnInit, OnDestroy {
  private subscriptions = new Subscription();

  ngOnInit() {
    // Add multiple subscriptions
    this.subscriptions.add(
      this.dataService.getData().subscribe(data => this.handleData(data))
    );
    
    this.subscriptions.add(
      this.userService.getUser().subscribe(user => this.handleUser(user))
    );
    
    this.subscriptions.add(
      interval(5000).subscribe(() => this.refresh())
    );
  }

  ngOnDestroy() {
    // Unsubscribe from all subscriptions at once
    this.subscriptions.unsubscribe();
  }
}

Conditional Unsubscription

import { fromEvent, Subscription } from 'rxjs';
import { take } from 'rxjs/operators';

let subscription: Subscription | null = null;

function startListening() {
  if (!subscription || subscription.closed) {
    subscription = fromEvent(document, 'click')
      .subscribe(event => console.log('Click:', event));
  }
}

function stopListening() {
  if (subscription && !subscription.closed) {
    subscription.unsubscribe();
  }
}

// Start listening
startListening();

// Stop after 5 seconds
setTimeout(stopListening, 5000);

Request Cancellation

import { ajax } from 'rxjs/ajax';
import { Subscription } from 'rxjs';

let requestSubscription: Subscription | null = null;

function searchUsers(term: string) {
  // Cancel previous request if still pending
  if (requestSubscription && !requestSubscription.closed) {
    requestSubscription.unsubscribe();
  }

  // Make new request
  requestSubscription = ajax(`/api/users?search=${term}`).subscribe({
    next: response => console.log('Results:', response),
    error: err => console.error('Search failed:', err)
  });
}

searchUsers('alice');
// User types quickly...
searchUsers('alice smith'); // Cancels previous request

Subscription Pool Pattern

import { Subscription, interval, fromEvent } from 'rxjs';

class SubscriptionPool {
  private subscriptions = new Subscription();

  add(subscription: Subscription): void {
    this.subscriptions.add(subscription);
  }

  unsubscribeAll(): void {
    this.subscriptions.unsubscribe();
    this.subscriptions = new Subscription(); // Reset for reuse
  }

  get isClosed(): boolean {
    return this.subscriptions.closed;
  }
}

// Usage
const pool = new SubscriptionPool();

pool.add(interval(1000).subscribe(x => console.log('Timer:', x)));
pool.add(fromEvent(document, 'click').subscribe(e => console.log('Click')));

// Clean up all at once
setTimeout(() => pool.unsubscribeAll(), 5000);

Memory Leak Prevention

Common Leak Scenarios

These are common sources of memory leaks in RxJS applications:
// ✗ BAD: Never completes, never unsubscribed
interval(1000).subscribe(x => console.log(x));

// ✗ BAD: Component destroyed but subscription lives on
class Component {
  ngOnInit() {
    interval(1000).subscribe(x => this.updateData(x));
  }
  // ngOnDestroy - no cleanup!
}

// ✗ BAD: Event listener never removed
fromEvent(window, 'resize').subscribe(e => this.handleResize(e));

Leak Prevention Strategies

class Component implements OnDestroy {
  private subscription = new Subscription();

  ngOnInit() {
    this.subscription.add(
      interval(1000).subscribe(x => console.log(x))
    );
  }

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

Best Practices

1. Always Unsubscribe from Infinite Streams

Infinite Observables (interval, fromEvent, etc.) must be manually unsubscribed unless they complete via operators.
// Infinite - needs unsubscribe
const sub1 = interval(1000).subscribe();
sub1.unsubscribe();

// Finite - auto unsubscribes
const sub2 = of(1, 2, 3).subscribe();
// No manual unsubscribe needed
class Dashboard implements OnDestroy {
  private subs = new Subscription();

  ngOnInit() {
    this.subs.add(this.loadUsers());
    this.subs.add(this.loadMetrics());
    this.subs.add(this.startPolling());
  }

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

3. Use Operators to Auto-Complete

import { takeUntil, take, takeWhile, first } from 'rxjs/operators';

// Take first value
stream$.pipe(first()).subscribe();

// Take n values
stream$.pipe(take(5)).subscribe();

// Take until condition
stream$.pipe(takeWhile(x => x < 100)).subscribe();

// Take until notifier
stream$.pipe(takeUntil(stop$)).subscribe();

4. Check Before Unsubscribing

if (subscription && !subscription.closed) {
  subscription.unsubscribe();
}

5. Avoid Subscription Nesting

// ✗ BAD: Nested subscriptions
users$.subscribe(user => {
  posts$.subscribe(posts => {
    comments$.subscribe(comments => {
      // Subscription hell!
    });
  });
});

// ✓ GOOD: Use operators
import { switchMap, map } from 'rxjs/operators';

users$.pipe(
  switchMap(user => posts$),
  switchMap(posts => comments$)
).subscribe(comments => {
  // Single subscription point
});

Common Patterns

Cleanup on Condition

import { interval, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

const stop$ = new Subject<void>();

interval(1000)
  .pipe(takeUntil(stop$))
  .subscribe(x => console.log(x));

// Stop when condition is met
if (someCondition) {
  stop$.next();
}

Reusable Subscription Manager

import { Subscription, Observable } from 'rxjs';

class SubManager {
  private subs = new Subscription();

  sink<T>(observable: Observable<T>, next: (value: T) => void): void {
    this.subs.add(observable.subscribe({ next }));
  }

  unsubscribe(): void {
    this.subs.unsubscribe();
    this.subs = new Subscription();
  }
}

// Usage
const manager = new SubManager();
manager.sink(data$, data => console.log(data));
manager.sink(user$, user => console.log(user));
manager.unsubscribe(); // Clean all

When to Unsubscribe

Observable TypeNeed to Unsubscribe?
of, from (array)No - completes immediately
interval, timer (infinite)Yes
fromEventYes
HTTP requests (single)No - completes after response
WebSocketYes
Subject (infinite)Yes
With take, first, takeUntilNo - operators handle it
  • Observable - Creates Subscriptions when subscribed to
  • Observer - Receives notifications during Subscription
  • Operators - Can automatically complete Subscriptions
  • Subject - Can be unsubscribed like any Subscription