Skip to content

BehaviorSubject#

A BehaviorSubject is a special type of Subject with two key distinctions:

  1. Requires an Initial Value: You must provide a starting value when you create a BehaviorSubject.
  2. Emits Current Value on Subscription: When a new Observer subscribes, a BehaviorSubject immediately emits its most recent value (the "current" value) to that new subscriber. If no next() calls have happened yet, it emits the initial value.

Think of it like a whiteboard or a notice board:

  • It always has something written on it (the initial value).
  • Whenever someone updates the board (next() is called), the message changes.
  • Anyone who walks up to the board (subscribes) immediately sees the message currently written on it. They don't have to wait for the next update to know the current status.

Like a regular Subject, it still multicasts – pushing values to all current subscribers when next() is called.

At a glance

  • Signature: new BehaviorSubject<T>(initialValue); also has a synchronous getValue()
  • Use when: modeling state that always has a current value: auth status, selected theme, active filters
  • Avoid when: there is no sensible initial value, or subscribers need more than one past value (use ReplaySubject)
  • Top gotcha: every new subscriber immediately receives the current value; code that expects to "wait for the next event" will fire instantly

Why Use a BehaviorSubject?#

BehaviorSubject is excellent for managing state because application state usually always has a current value.

  1. Representing State: Perfect for things like:
    • Current user authentication status (e.g., null initially, then a User object).
    • Currently selected theme ('light' or 'dark').
    • The latest value of filters applied to a list.
    • Configuration settings.
  2. Providing Initial Data: Components often need to know the current state as soon as they initialize to render correctly. BehaviorSubject guarantees they get a value immediately upon subscription.
  3. Synchronous Access (Use Sparingly): BehaviorSubject has a getValue() method that allows you to synchronously get its current value without subscribing. While useful occasionally, relying heavily on this can be an anti-pattern compared to reactive subscriptions. Signals often provide a better way to get current values reactively.

Minimal Example#

import { BehaviorSubject } from "rxjs";

const theme = new BehaviorSubject<"light" | "dark">("light");

theme.subscribe((v) => console.log(`A: ${v}`)); // A: light  (current value, immediately)

theme.next("dark"); // A: dark

theme.subscribe((v) => console.log(`B: ${v}`)); // B: dark   (late subscriber still gets current value)

console.log(theme.getValue()); // dark (synchronous read)

Real-World Example: Application Theme Service#

Let's create a service that manages the application's theme (e.g., 'light' or 'dark'). The theme always has a current state, and components need to know the current theme immediately when they load.

Code Snippets#

1. Theme Service (theme.service.ts)

import { Injectable } from "@angular/core";
import { BehaviorSubject, Observable } from "rxjs";

export type AppTheme = "light" | "dark";

@Injectable({
  providedIn: "root",
})
export class ThemeService {
  // 1. Define the initial theme state.
  private initialTheme: AppTheme = "light";

  // 2. Create the BehaviorSubject with the initial value.
  // Private to control who can emit new values.
  private themeSubject = new BehaviorSubject<AppTheme>(this.initialTheme);

  // 3. Expose the theme state as an Observable for components to subscribe to.
  // '.asObservable()' prevents external code from calling .next() on our subject.
  theme$: Observable<AppTheme> = this.themeSubject.asObservable();

  // Method to change the theme
  setTheme(newTheme: AppTheme): void {
    if (newTheme !== this.getCurrentTheme()) {
      // Optional: Prevent emitting same value
      console.log(`ThemeService: Setting theme to ${newTheme}`);
      // 4. Emit the new theme value to all subscribers.
      this.themeSubject.next(newTheme);
    }
  }

  // Method to get the current value synchronously (use with caution)
  getCurrentTheme(): AppTheme {
    return this.themeSubject.getValue();
  }
}

2. Theme Switcher Component - Changes the theme

import { Component, inject } from "@angular/core";
import { ThemeService, AppTheme } from "./theme.service"; // Adjust path

@Component({
  selector: "app-theme-switcher",
  template: `
    <div>
      <label>Select Theme: </label>
      <button (click)="setTheme('light')" [disabled]="currentTheme === 'light'">
        Light
      </button>
      <button (click)="setTheme('dark')" [disabled]="currentTheme === 'dark'">
        Dark
      </button>
      <p>
        <small>Current theme in service: {{ currentTheme }}</small>
      </p>
    </div>
  `,
  styles: [
    "div { margin-bottom: 15px; padding: 10px; border: 1px dashed grey; }",
  ],
})
export class ThemeSwitcherComponent {
  private themeService = inject(ThemeService);

  // Get the current theme synchronously for the button state (demonstration)
  get currentTheme(): AppTheme {
    return this.themeService.getCurrentTheme();
  }

  setTheme(theme: AppTheme): void {
    this.themeService.setTheme(theme);
  }
}

3. Themed Content Component - Reacts to the theme

import {
  Component,
  inject,
  signal,
  OnInit,
  DestroyRef,
  ChangeDetectionStrategy,
  HostBinding,
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ThemeService, AppTheme } from "./theme.service"; // Adjust path

@Component({
  selector: "app-themed-content",
  template: `
    <div class="content">
      <h3>Themed Content</h3>
      <p>
        My current theme is: <strong>{{ currentThemeSignal() }}</strong>
      </p>
      <p>I received this theme immediately when I loaded!</p>
    </div>
  `,
  styles: [
    `
      :host(.dark-theme) .content {
        background-color: #333;
        color: white;
        border: 1px solid yellow;
      }
      :host(.light-theme) .content {
        background-color: #f4f4f4;
        color: black;
        border: 1px solid blue;
      }
      .content {
        padding: 20px;
        margin-top: 10px;
        transition: all 0.3s ease;
      }
    `,
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ThemedContentComponent implements OnInit {
  private themeService = inject(ThemeService);
  private destroyRef = inject(DestroyRef);

  // Signal to hold the current theme for the template
  currentThemeSignal = signal<AppTheme>("light"); // Initialize with default

  // Use HostBinding to apply theme class to the component's host element
  @HostBinding("class.dark-theme") isDark = false;
  @HostBinding("class.light-theme") isLight = true;

  ngOnInit(): void {
    // Subscribe to the theme state from the service
    this.themeService.theme$
      .pipe(
        // Automatically unsubscribe when the component is destroyed
        takeUntilDestroyed(this.destroyRef),
      )
      .subscribe((theme) => {
        // *** Key Point ***
        // This code runs IMMEDIATELY when ngOnInit executes because
        // BehaviorSubject emits the current value ('light') upon subscription.
        console.log(`ThemedContentComponent received theme: ${theme}`);

        // Update the signal for the template
        this.currentThemeSignal.set(theme);

        // Update host bindings for styling
        this.isDark = theme === "dark";
        this.isLight = theme === "light";
      });
  }
}

4. App Component

import { Component } from "@angular/core";
import { ThemeSwitcherComponent } from "./theme-switcher.component"; // Adjust path
import { ThemedContentComponent } from "./themed-content.component"; // Adjust path

@Component({
  selector: "app-root",
  imports: [ThemeSwitcherComponent, ThemedContentComponent], // Import components
  template: `
    <h1>RxJS BehaviorSubject Demo</h1>
    <app-theme-switcher></app-theme-switcher>
    <app-themed-content></app-themed-content>
  `,
})
export class AppComponent {}

Explanation:

  1. ThemeService creates a BehaviorSubject called themeSubject initialized with 'light'.
  2. It exposes theme$ as an observable.
  3. When ThemedContentComponent initializes (ngOnInit), it subscribes to themeService.theme$.
  4. Crucially: Because it's a BehaviorSubject, the subscription immediately receives the current value ('light') without waiting for setTheme to be called. The component can instantly update its currentThemeSignal and apply the correct styling.
  5. When the user clicks buttons in ThemeSwitcherComponent, themeService.setTheme() is called.
  6. This calls themeSubject.next(newTheme), broadcasting the new theme to all subscribers.
  7. ThemedContentComponent's subscription receives the new theme, updates its signal (currentThemeSignal), and its appearance changes accordingly.

Compare this to a regular Subject: If we used a plain Subject without an initial value, ThemedContentComponent wouldn't know the theme when it first loaded. It would only react after the user first clicked a button in the ThemeSwitcherComponent. BehaviorSubject solves this by providing that essential current state immediately.

Common Mistakes#

Overusing getValue(). Sprinkling synchronous reads through the codebase turns reactive state back into imperative state and hides ordering bugs. Prefer subscribing (or toSignal) and let values flow; reserve getValue() for rare boundary cases.

Choosing a misleading initial value. new BehaviorSubject<User>(null!) forces every subscriber to handle a fake null state. If "no value yet" is real, model it honestly (User | null) or use a ReplaySubject(1), which has no initial value but replays the first real one.

Using it as a public read-write bag. Same rule as Subject: keep it private, expose asObservable(), and mutate only through service methods so state changes stay traceable.

Interview Q&A#

When do you pick BehaviorSubject over Subject?

When the stream models state rather than events: state always has a current value, and any component subscribing later still needs it. Events (clicks, notifications) fit a plain Subject, where replaying the last event would be wrong.

What is the difference between BehaviorSubject(1) semantics and ReplaySubject(1)?

Both replay one value to new subscribers. BehaviorSubject requires an initial value and exposes getValue(). ReplaySubject(1) starts empty (no emission until the first next) and, importantly, still replays its last value to new subscribers after completion, while a completed BehaviorSubject only emits complete.

How does BehaviorSubject relate to Angular signals?

Both hold a current value and notify consumers of changes. A writable signal() covers most local-state cases with less ceremony, and toSignal()/toObservable() bridge the two worlds. BehaviorSubject remains useful when you need RxJS operators, multicast semantics, or interop with stream-based APIs.