RxJS - Subject vs. BehaviorSubject vs. ReplaySubject

RxJS - Subject vs. BehaviorSubject vs. ReplaySubject

Learn how to choose between Subject, BehaviorSubject, and ReplaySubject.

Subject, BehaviorSubject, and ReplaySubject are all supersets of the RxJs Observable that allow us to push new values through the observable pipe via the next function.

Subject

Subject doesn't hold a value, so it doesn't emit any value to new subscribers. This means that any previous values sent through the observable won't reach new subscribers. Late subscribers don't receive previously emitted values.

When would I use Subject?

You might use this when you don't need to keep any value and are only interested in the value at the moment something happens.

It's helpful in scenarios like component-to-component communication, for signaling events, like a button click, where knowing past values (if the button was clicked before) isn't necessary.

Subject Example:

import { Subject } from 'rxjs';

describe('RxJS Subject Test', () => {
  it('should demonstrate that late subscribers do not receive previously emitted values', done => {
    const subject = new Subject<number>();

    // First subscriber
    subject.subscribe({
      next: (v) => {
        expect(v).toEqual(1);
      }
    });

    // Emit a value
    subject.next(1);

    // Late subscriber
    subject.subscribe({
      next: (v) => {
        // This should not be called for the first emitted value
        fail('Late subscriber should not receive previously emitted values');
      }
    });

    // Complete the subject to end the test
    subject.complete();
    done();
  });
});

BehaviorSubject

BehaviorSubject stores a single value and can start with an initial value when created. Any new subscribers will get the most recent value as soon as they subscribe.

  • Stores one value

  • New subscribers get the most recent value

  • You can get the current value with the getValue() function. For example, new BehaviorSubject('hello').getValue() would give you 'hello'. This is useful if you need the current value without subscribing to the BehaviorSubject.

Why would I useBehaviorSubject?

I generally use this when I want the latest value emitted and to set the first value that is emitted.

Note, however, if you initialize the BehaviorSubject with null (like new BehaviorSubject(null)), the buffer value will be null. This means new subscribers will get a null value when they subscribe. If you don't want this, think about using a ReplaySubject or adding a filter operator to your subscriber.

BehaviorSubject Example:

import { BehaviorSubject } from 'rxjs';

describe('BehaviorSubject', () => {
  it('should start with an initial value and update when new values are pushed', () => {
    const initialValue = 0;
    const subject = new BehaviorSubject<number>(initialValue);

    let currentValue = subject.getValue();
    expect(currentValue).toBe(initialValue);

    const newValue = 1;
    subject.next(newValue);
    currentValue = subject.getValue();
    expect(currentValue).toBe(newValue);
  });
});

ReplaySubject

ReplaySubject stores multiple values in its buffer, unlike the BehaviorSubject which can only hold one. A ReplaySubject(1) (with a buffer size of 1) acts similarly to a BehaviorSubject('myInitialValue'), but it doesn't set an initial value. Therefore, new subscribers won't get any emitted value until at least one value has been sent through.

  • Can store one or several values in its buffer.

  • New subscribers get all the values stored in the buffer when they subscribe.

  • It doesn't start with a default value, meaning subscribers won't receive any value until something is emitted.

Why would I use ReplaySubject?

You might choose ReplaySubject if you want to record the last N emissions from the observable. This is useful if you're interested in keeping track of the most recent value (ReplaySubject(1)), similar to BehaviorSubject, but without the need to set an initial value for subscribers.

It's important to note that, different from BehaviorSubject, ReplaySubject doesn't have a getValue() function. This means you need to subscribe to the observable to access the values in its buffer.

ReplaySubject Example:

import { ReplaySubject } from 'rxjs';

describe('ReplaySubject Test', () => {
  it('should replay the last emitted values to new subscribers', done => {
    const replaySubject = new ReplaySubject<number>(2); // Buffer size of 2
    const emittedValues: number[] = [];

    // Emit some values
    replaySubject.next(1);
    replaySubject.next(2);
    replaySubject.next(3);

    // New subscriber should receive the last 2 emitted values (2, 3)
    replaySubject.subscribe({
      next: value => emittedValues.push(value),
      complete: () => {
        expect(emittedValues).toEqual([2, 3]);
        done();
      }
    });

    replaySubject.complete();
  });
});