Give BehaviorSubject initial value - value being an Observable
12,860
I would do it like
private user = new BehaviorSubject<User>(null);
public user$ = this.user.asObservable();
constructor() {
this.getUser();
}
getUser(): User {
// does obviously not work!
return this.apiService.getUser()
.subscribe(data => {
if(!!data) {
this.user.next(data); // <<== added
}
// do something else
})
}
Author by
DRNR
Updated on June 05, 2022Comments
-
DRNR almost 2 years
I know that I cannot give
BehaviorSubject
an Observable value, but I need a way to solve this issue. On app initialization I am fetching current user (if exists), and I need to give theBehaviorSubject
that potential value. So my service code looks like this:private user = new BehaviorSubject<User>(this.getUser()); public user$ = this.user.asObservable(); getUser(): User { // does obviously not work! return this.apiService.getUser() .map(data => { if(data) { return data; } // do something else }) }
So is there some magical rxjs operator to solve this issue, or some other possibility?
Thanks in advance!
-
Jota.Toledo over 6 yearswhat is the return type of the getUser method in the api service?
-
Jota.Toledo over 6 yearsAssuming that getUser will return an Observable<User>, you are AFAIK obligated to subscribe somewhere in your code (most likely constructor method) to give that BehaviorSubject an "initial value" (the value wont be available at the instantiation moment, will be passed by "next" call). So basically that getUser(): User method is impossible to implement.
-
olivarra1 over 6 years@Jota.Toledo Not quite obligated: Can be achieved by concatenating observables. Take a look at my answer
-
-
Günter Zöchbauer over 6 years@Jota.Toledo thanks for the update, but I think
do()
also won't cause the observable to do anything. I changed it tosubscribe()
. -
Jota.Toledo over 6 yearsyup, just wanted to point that out. I guess the getUser returns an observable, so in this case you need to modify your answer still: you are returning a subscription,not an User instance
-
Jota.Toledo over 6 yearsIll take a look later. Could you create a plnkr or fiddle to test this?
-
bryan60 over 6 yearsCould also add .filter(v => !!v) if you don’t want the initial null value to go through, depends on the case though.
-
bryan60 over 6 yearsThis won’t work, it will run the api call on every subscription, which isn’t the goal. You’d need a second subject and a store pattern to make this work the way you intend
-
Jota.Toledo over 6 yearsWhere do you push values to the subject?
-
olivarra1 over 6 yearsI'm not pushing, OP does need that to be a subject, I guess that's because he wants to push. Else why bother creating a subject when you can instead just
const user$ = this.getUser()
? -
olivarra1 over 6 yearsOP Says "I need to initialize a Subject with something that comes from an observable". This is the way to do it. @Jota.Toledo plunkr: next.plnkr.co/edit/chVpEzUfm5uRZ13j
-
olivarra1 over 6 years@bryan60 could use a
ReplaySubject
instead of aBehaviourSubject
for that matter.... -
olivarra1 over 6 years@bryan60 That depends on use case, but you can always
.publish()
the observable so it gets shared between subscribers -
Jota.Toledo over 6 yearsthe problem that I see with this is that the first observer that starts to get values from "user$" wont get notified when new values are pushed into "user", as it will be taking values from this.getUser() reference
-
bryan60 over 6 yearsNot true, the nature of switchMap, it switches when new values come through and cancels previous inner observables. One cool thing about this pattern is that you can cause server refreshes by passing null values through the source, due to the switching.
-
bryan60 over 6 years@Jota.Toledo FYI, your edit created an infinite loop, the way I had it is correct.
-
bryan60 over 6 yearsActually, no not an infinite loop, just an odd double subscription, but I believe the way I have it is semantically cleaner.
-
olivarra1 over 6 yearsI really don't think this is semantically cleaner... You have at least 2 side effects in a single line. Consider this, for instance, on the first loop,
u = null
, will get throughthis.getUser()
. But thisgetUser()
after thedo()
will yield one user that will be yielded touser$
because ofswitchMap
. But insidedo()
you also call.next(nu)
, which will causeuser$
to yield an extra user for the same one. If it's not like that, then I don't consider this semantically correct either - it's really complex to understand what's going on in there. -
bryan60 over 6 yearsit's one side effect done intentionally, the way this actually plays outin practice is that the subscriber never gets the result of the getUser() call, because the next operation immediately cancels that subscription in the same thread and just reverts to the value that got passed through the source in the do operation, so the subscriber will only actually see one event as intended. It's a little complicated, but I actually think it's clear, subsscribe to the source, if it doesn't have a value, switch to this remote fetch method and save the value.
-
bryan60 over 6 yearsit's essentially the classic cache get and set scheme in observable format.
-
olivarra1 over 6 years@bryan60 Well, in plunkr doesn't seem to work next.plnkr.co/edit/7ynJ7opLCQjqB3Gk (has the exact behaviour I described: duplicated initial value). With all respects, if you're not too sure why does this doesn't work, I wouldn't call this solution "semantically correct". It's far from it.
-
olivarra1 over 6 yearsAnd another issue, what if you call
.next(null)
on purpose to set the user to null? This would refetch the user (and again, yield it twice in the result stream) -
olivarra1 over 6 yearsLet us continue this discussion in chat.
-
bryan60 over 6 yearsGood point, this is still recoverable though, and it isn't necesarily bad if events trigger twice, depends on the case. and doens't have the drawback of repeating the API call on subsequent subscriptions, see my update.
-
bryan60 over 6 yearscausing the user to refresh by setting it to null is a feature, not a bug
-
Günter Zöchbauer over 6 yearsThe return value isn't used at all. The
next()
is the relevant part -
DRNR over 6 yearsThank you for your answer, I will get back to you ASAP! :)
-
DRNR over 6 yearsThank you for your answer, I will get back to you ASAP! :)
-
DRNR over 6 yearsThank you for your answer, I will get back to you ASAP! :)
-
tif almost 5 years@GünterZöchbauer I've seen examples where they call getUser in the component on ngOnInit method. What is a better approach? In the consturctor of the service or in ngOnInit of the component using it?
-
Günter Zöchbauer almost 5 yearsIf it depends on a value passed to an input, then you need
ngOnInit
, otherwise I don't see any advantage/disadvantage. It might be easier to unit-test if you keep code out of the constructor. It's mostly personal preference. -
Günter Zöchbauer almost 5 yearsIf it depends on a value passed to an input, then you need
ngOnInit
, otherwise I don't see any advantage/disadvantage. It might be easier to unit-test if you keep code out of the constructor. It's mostly personal preference.