Reputation: 962
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:
Subject
, but it doesn't emit the old value immediately after subscribing to it like BehaviorSubject
and I can't retrieve the current value.ReplaySubject
with a buffer of size 1, but this also doesn't allow retrieval of the current value.BehaviorSubject.create()
instead of new BehaviorSubject<Whatever>(null)
but this just doesn't do anything when calling next()
. No value is emitted and the subscription code never fires.BehaviorSubject
with .pipe(skip(1)).subscribe
but if I run this code after I called next()
then I loose that old but valid value, only firing after next
is called again.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
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
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