Issue
I have a service that exposes some global state using an observable. The state must be initialized by making an HTTP call. I want all consumers of the state to receive the same value, receive the current value when they subscribe to the observable, and receive updates when the state changes.
This scenario is typically handled using a Subject
, which can be initialized by subscribing it to the observable returned by the HTTP request.
However, I don't want the HTTP request to be executed until the first time something subscribes to the global state observable. If you use the subscribe
method of the Subject
, the HTTP request is called then.
I have the following code in the service. This works, but it seems like there should be a simpler way to do this.
private initialized = false;
private readonly subject = new ReplaySubject<string>(1);
constructor(private readonly httpClient: HttpClient) {}
get data$(): Observable<string> {
return new Observable<string>(subscriber => {
if (!this.initialized) {
this.refresh();
}
let subscription = this.subject.subscribe(value => subscriber.next(value));
return () => subscription.unsubscribe();
});
}
refresh(): void {
this.httpClient.get<string>('https://...')
.pipe(
tap(() => this.initialized = true)
)
.subscribe(value => this.subject.next(value));
}
Sandbox: https://codesandbox.io/s/reverent-almeida-d2zbjx?file=/src/app/test.service.ts
Solution
The following should accomplish what you want. The data$ field should be the merge of two observables. The first will be the initial http call and the other will be listening to emission from refreshSubject to call the service again.
The initial call won't execute until the first subscription is made and any call to refreshSubject will be ignored as well.
Finally, using shareReplay will cause all subsequent subscribers to get the last result. The refCount parameter is used so that if subscriber count becomes zero, the subscription is completely unsubscribed, and the next subscriber will initiate the first call again. If this behavior is not desirable, you can just use shareReplay(1)
which will always have the last result and continue to listen to refreshSubject.
readonly refreshSubject = new subject<void>();
readonly data$ = merge(
this.httpClient.get<string>('https://...'),
this.refreshSubject.pipe(
switchMap(() => this.httpClient.get<string>('https://...'))
)
).pipe(
shareReplay({ bufferSize: 1, refCount: true })
);
Answered By - Daniel Gimenez
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.