Skip to content

Angular Subjects

A Subject is both an Observable and a way to manually push new values into that stream, useful for building simple event buses and shared state services. This lesson covers the three most common Subject variants.

What Is a Subject?

A Subject is a special kind of Observable that is also an "Observer": it has .next(), .error(), and .complete() methods you can call directly to push values into the stream, which every current subscriber then receives.

This makes Subjects a natural fit for services that need to broadcast events or shared state to multiple, independent parts of an application.

import { Subject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class NotificationService {
  private notification$ = new Subject<string>();
  readonly notifications = this.notification$.asObservable();

  notify(message: string) {
    this.notification$.next(message);
  }
}

Exposing .asObservable() publicly (rather than the raw Subject) prevents outside code from calling .next() on it directly, only the service itself should push new values.

Subject Variants

new Subject<T>()               // no memory of past values, only live subscribers get new emissions
new BehaviorSubject<T>(initial) // remembers the current value; new subscribers get it immediately
new ReplaySubject<T>(n)         // remembers the last n values for new subscribers
  • A plain Subject has no memory, subscribers only receive values emitted after they subscribed.
  • BehaviorSubject requires an initial value and always has a "current value" accessible via .value.
  • ReplaySubject replays a configurable number of past emissions to any new subscriber.
  • All three share the same .next()/.error()/.complete() API for pushing values.

Subjects Cheatsheet

Choosing the right Subject variant for your use case.

Type Remembers Best For
Subject Nothing One-off events, like a "button clicked" bus
BehaviorSubject The current (latest) value Shared state with a required initial value
ReplaySubject The last N values State where new subscribers need recent history
.next(value) n/a Push a new value into the stream
.value n/a Synchronously read the current value (BehaviorSubject only)
.asObservable() n/a Expose a Subject as a read-only Observable

BehaviorSubject for Shared State

BehaviorSubject is especially common for service-based shared state, since it always has a current value, any component that subscribes (even after the value was last set) immediately receives the latest state rather than waiting for the next change.

@Injectable({ providedIn: 'root' })
export class CartService {
  private itemsSubject = new BehaviorSubject<string[]>([]);
  readonly items$ = this.itemsSubject.asObservable();

  addItem(item: string) {
    this.itemsSubject.next([...this.itemsSubject.value, item]);
  }
}

this.itemsSubject.value synchronously reads the current cart contents without needing to subscribe.

A Plain Subject as an Event Bus

A plain Subject is ideal for one-off, fire-and-forget events that unrelated parts of the app might want to react to, without needing to remember or replay past events to late subscribers.

@Injectable({ providedIn: 'root' })
export class UiEventBus {
  private events$ = new Subject<'sidebar-toggled' | 'theme-changed'>();
  readonly events = this.events$.asObservable();

  emit(event: 'sidebar-toggled' | 'theme-changed') {
    this.events$.next(event);
  }
}

Subjects vs Signals for Shared State

For simple, synchronous shared state, Angular signals are often a simpler alternative to a BehaviorSubject, no .asObservable()/.value distinction, and automatic reactivity in templates without an async pipe. Reach for Subjects when you specifically need RxJS's operator ecosystem (debouncing, combining streams, etc.) around that state.

Common Mistakes

  • Exposing a raw Subject/BehaviorSubject publicly instead of .asObservable(), letting any consumer push arbitrary values into shared state.
  • Using a plain Subject when new subscribers need the current/latest value immediately, BehaviorSubject is the correct choice there.
  • Forgetting to call .complete() on Subjects that represent a finite, bounded stream, leaving them open indefinitely.
  • Reaching for a Subject-based service when a simple signal would model the same shared state more simply.

Key Takeaways

  • A Subject is both an Observable and an Observer, you can subscribe to it and call .next() on it.
  • BehaviorSubject remembers and immediately replays its current value to new subscribers.
  • ReplaySubject replays a configurable number of past values to new subscribers.
  • Expose Subjects as .asObservable() publicly to keep write access (.next()) private to the owning service.

Pro Tip

Keep the writable Subject/BehaviorSubject private on a service, and expose only the read-only .asObservable() stream publicly; this enforces that state changes only happen through the service's own methods, not from arbitrary consumers.