Frequently, in angular app, i have some service, which need to retreive some data through http request and share it to consumers through BehaviorSubject. It have implementation
What about initializing a stream in the constructor that is made hot by an initial call to getData
? Then the first result is cached in the ReplaySubject
class Service {
private init = new Subject();
private data = new ReplaySubject(1);
constructor() {
this.init.pipe(
take(1),
switchMap(() => anyHttpCall())
)
.subscribe(res => this.data.next(res));
}
getData() {
this.init.next();
return this.data.asObservable();
}
}
The best way is using rxjs shareReplay. This operator returns an Observable that shares a single subscription to the underlying source. In other words, turns our observable hot.
const CACHE_SIZE = 1;
class Service {
private _data$: Observable<YourType>;
get data(): Observable<YourType> {
if (!this._data$) {
this._data$ = anyHttpCall()
.pipe(shareReplay({ refCount: true, bufferSize: CACHE_SIZE })
);
}
return this._data$;
}
}
The bufferSize determines the maximum element count of the replay buffer, that is the number of elements that are cached and replayed for every subscriber.
This post explains this very good: https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html
What about your options:
1) Should work but as for me it requires a lot of code written by hand.
2) Imagine user won't call getData
method but you have already sent redundant request.
There is a very convenient operator shareReplay
that will help you make your cold observable hot.
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
export class Service {
private cache$: Observable<any>;
...
getData() {
if (!this.cache$) {
this.cache$ = this.anyHttpCall().pipe(
shareReplay(1)
);
}
return this.cache$;
}
}
Ng-run Example