4
votes

I have the following requirement.

I have An Angular service with an BehaviorSubject. A http request is done and when this is done the BehaviorSubject.next method is invoked with the value. This value can change during the lifecycle of the single page.

Different subscribers are registered to it and get invoked whenever this changes.

The problem is that while the http request is pending the BehaviorSubject already contains a default value and subscribers are already immediately getting this value.

What I would want is that subscribers have to wait till the http request is done (deferred) and get the value when the http request is done and sets the value. So what I need is some kind of deferred Behavior subject mechanism.

How would i implement this using rxjs?

Another requirement is that if I subscribe to the behaviorsubject in a method we want the subcriber to get the first non default value and that the subscription ends. We don't want local subscriptions in functions to be re-executed.

2
Why don't you directly expose the Observable returned by the http call and need to go through a BehaviorSubject? - Picci
It sounds like you can just switch to ReplaySubject(1) and that's it. - martin
Actually the http call doesn't create the value in the BehaviorSubject. It performs a backend call for authentication creates a cookie (legacy code) and when http call is done we compose an object based on the information within the cookie. Also due to some other non http actions within the application the values of this object can change (without http requests anymore). - stephan.peters
Subscribers are unaware whether the value changes due to a http request or non-http actions. They just need to get the latest value. - stephan.peters
I'm playing around with the ReplaySubject. What I also want is that if in a method a subscription is done to the subject: We wait till the first value is emitted and than we unsubcribe to prevent the subscription in the method to be called over and over again. - stephan.peters

2 Answers

1
votes

Use a filter on your behavior subject so your subscribers won't get the first default emitted value:

mySubject$: BehaviorSubject<any> = new BehaviorSubject<any>(null);

httpResponse$: Observable<any> = this.mySubject$.pipe(
  filter(response => response)
  map(response => {
     const modifyResponse = response;
    // modify response
    return modifyResponse;
  }),
  take(1)
);
this.httpResponse$.subscribe(response => console.log(response));

this.myHttpCall().subscribe(response => this.mySubject$.next(response));

You can of course wrap the httpResponse$ observable in a method if you need to.

0
votes

I think the fact that you want to defer the emitted default value, straight away brings into question why you want to use a BehaviorSubject. Let's remember: the primary reason to use a BehaviorSubject (instead of a Subject, or a plain Observable), is to emit a value immediately to any subscriber.

If you need an Observable type where you need control of the producer (via .next([value])) and/or you want multicasting of subscription out of the box, then Subject is appropriate.

If an additional requirement on top of this is that subscribers need a value immediately then you need to consider BehaviorSubject.

If you didn't say you need to update the value from other non-http events/sources, then I would've suggested using a shareReplay(1) pattern. Nevertheless...

private cookieData$: Subject<RelevantDataType> = new 
Subject<RelevantDataType>(null);


// Function for triggering http request to update
// internal Subject.
// Consumers of the Subject can potentially invoke this 
// themselves if they receive 'null' or no value on subscribe to subject
public loadCookieData(): Observable<RelevantDataType> {
    this.http.get('http://someurl.com/api/endpoint')
        .map(mapDataToRelevantDataType());
}

// Function for dealing with updating the service's 
// internal cookieData$ Subject from external 
// consumer which need to update this value
// via non-http events
public setCookieData(data: any): void {
    const newCookieValue = this.mapToRelevantDataType(data); // <-- If necessary
    this.cookieData$.next(newCookieValue); // <-- updates val for all subscribers
}

get cookieData(): Observable<RelevantDataType> {
    return this.cookieData$.asObservable();
}

The solution is based on OPs comments etc. - deals with subscribing to subject type. - deals with external subscribers not being able to 'next' a new value directly - deals with external producers being able to set a new value on the Subject type - deals with not giving a default value whilst http request is pending