user1969903
user1969903

Reputation: 962

Is there an equivalent to BehaviorSubject that doesn't require an initial value and is able to provide the current value?

Looking for the same functionality as BehaviorSubject but without having to provide an initial value.

I often need to subscribe to a subject before my data gets loaded from whatever source I load it from. I also need the subscription to fire immediately after subscribing if and only if next() was previously called on the subject and I expect it to show me the last emitted value, as well as be able to get the current value synchronously at any point in time, similar to the value field of BehaviorSubject.

Here's what I tried:

The only solution I came up with is to extend the ReplaySubject class to add a private field for the current value, which gets set when next() is called, and a getter to retrieve it.

I always expect these values to be defined and valid, so I don't understand why I have to provide a default null/undefined value to BehaviourSubject for an object type that I specifically expect to not be undefined/null and explicitly defined them accordingly, and then write an if statement in every subscription to check every time a new value is emitted if it is valid.

I also don't understand why must it emit that null value and cause each subscription to fire for no good reason each time I subscribe to the subject before I load the data.

So is there any alternative I have missed?

Edit: So far, the answer to my question seems to be that no, there isn't an equivalent. As such, I'm providing my work around for anyone who's looking for the same behavior:

import { ReplaySubject } from 'rxjs';

export class SensibleSubject<T>
{
    private replaySubject: ReplaySubject<T>;
    private _canGetValue = false;
    private _value!: T;

    public get value()
    {
        if (!this._canGetValue)
            throw new Error("Attempted to retrieve value before it has been set!");

        return this._value;
    }

    constructor()
    {
        this.replaySubject = new ReplaySubject<T>(1);
    }

    public next(value: T)
    {
        this._canGetValue = true;
        this._value = value;
        this.replaySubject.next(value);
    }

    public asObservable()
    {
        return this.replaySubject.asObservable();
    }

    public canGetValue()
    {
        return this._canGetValue;
    }
}

Initially, I specified that I extended the ReplaySubject class but, as crowmagnumb points out in his answer to Behaviour subject initial value null?, overriding next on the ReplaySubject somehow breaks it. I instead decided to wrap the ReplaySubject and expose the desired methods and fields.

Upvotes: 5

Views: 4240

Answers (2)

Andr&#233; C. Andersen
Andr&#233; C. Andersen

Reputation: 9405

I was looking for a similar solution, but could not find one. I've been using ReplaySubject<T>(1) for this. I got tired of the boilerplate, and not having access to the current value. This works well for me:

import { ReplaySubject } from "rxjs";

export class SensibleSubject<T> extends ReplaySubject<T> {
  public value: T | null = null;

  constructor() {
    super(1);
  }

  override next(value: T) {
    this.value = value;
    super.next(value);
  }
}

The downside is that value can be externally modified, but eh, we're all consenting adults.

Upvotes: 0

Barremian
Barremian

Reputation: 31125

How about

src = new BehaviorSubject<any>(null);
obs$ = src.asObservable.pipe(filter(value => !!value));

Since you say all the values emitted to the source will be valid and not undefined, the filter pipe will only filter the initial default null. This way you don't have to do an if check in each subscription and still get to keep the synchronous getValue() function.

Upvotes: 0

Related Questions