RxjS shareReplay : how to reset its value?
Asked Answered
W

3

19

I use shareReplay to call only once (like a cache) a webservice to retrieve some informations :

In my service :

getProfile(): Observable<Customer> {
    return this.callWS().pipe(shareReplay(1));
}

In multiple components :

this.myService.getProfile().subscribe(customer => {
    console.log('customer informations has been retrieved from WS :', customer);
});

Now I want to add a method to force refresh the informations (bypass shareReplay only once). I tried with storing my observable in a variable, and set it to null before re-initialize it, but it seems to break components subscriptions..

Any help ?

Thanks

Willettawillette answered 24/1, 2019 at 14:1 Comment(3)
I would use a state store (ngrx, ngxs)Actinometer
I am having the same problem.. any updates edit your question.. thanksEzaria
Have a look at: https://mcmap.net/q/665493/-resetting-replaysubject-in-rxjs-6Gallardo
B
42

I know this thread is old, but I think I know what the other answer meant about prepend a "reset" subject to push new values. Check this example:

private _refreshProfile$ = new BehaviorSubject<void>(undefined);

public profile$: Observable<Customer> = _refreshProfile$
  .pipe(
    switchMapTo(this.callWS()),
    shareReplay(1),
   );

public refreshProfile() {
  this._refreshProfile$.next();
}

In the above snippet, all profile$ new subscribers will receive the latest emitted value (upon calling callWS() once). If you wish to "refresh" the Customer being shared, you would call "refreshProfile()". This would emit a new value going through switchMapTo, reassigning the replay value and also notifying to any profile$ open subscriber.

Have a nice one

Barbarossa answered 7/5, 2019 at 23:35 Comment(1)
very nice solutionGetz
T
5

The other answers are all fine, but rather than having to reset the actual shareReplayed observable, a simpler approach might just be caching the result like so:

protected profile$: Observable<Customer>;

getProfile$(): Observable<Customer> {
  if (!this.profile$) {
    this.profile$ = this.callWS().pipe(shareReplay(1));
  }

  return this.profile$;
}

resetProfile() {
  this.profile$ = null;
}

ref: https://blog.angularindepth.com/fastest-way-to-cache-for-lazy-developers-angular-with-rxjs-444a198ed6a6

Technic answered 7/8, 2019 at 13:55 Comment(5)
Won't setting this.profile = null; stop updates for pre-existing subscribers?Cockahoop
@xcvbn existing subscribers only get a single emission in this scenario, so that's not an issue-Technic
The performance impact might be negligible, but do note that if getProfile is involved in a view, that it runs on every change detection cycle. You basically move the "complexity" from the reset to the retrieve, which is probably fine in most cases - but depending on the context, that might be relevant.Kid
Usually I would think getProfile$ wouldn't be called directly from the view, you would have profile$ = getProfile$() as a class property, and then update profile$ to a new getProfile$ if you need to refreshTechnic
The big drawback here is that each subscriber that receives the profile will only ever have that profile (until they re-subscribe). The refresher approach allows you to ensure all previous active subscribers receive the new value immediately.Organization
S
3

Sounds like you can prepend a Subject that will inject the new value with merge:

private refresh$ = new Subject();

refreshProfile(...): void {
  this.refresh$.next(/* whatever */);
}

getProfile(): Observable<Customer> {
  return this.callWS().pipe(
    merge(this.refresh$),
    shareReplay(1),
  );
}
Sternmost answered 24/1, 2019 at 14:24 Comment(1)
Don't know how it works but it works. I was having the same problem.Ezaria

© 2022 - 2024 — McMap. All rights reserved.