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.

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.

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",
  standalone: true,
  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",
  standalone: true,
  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",
  standalone: true,
  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.