Reputation: 791
There is a config data in project which is used by multiple components and services. The idea is to build a service which gets just once the data from API and provides it cached every time is needed.
private getConfig(): Observable<InitConfig> {
return this.http.get('public/config/initconfig')
.pipe(
tap((config: InitConfig) => {
this.config = config; // set the config
}),
);
}
public getInitConfig(): Observable<InitConfig> {
if ( _.isEmpty(this.config) ) {
return this.getConfig();
} else {
return of(this.config);
}
}
The problem is when the get request is on progress and the data is not cached yet. If another consumer calls getInitConfig() at this time I get duplicated get request.
Upvotes: 3
Views: 2149
Reputation: 3571
Here is a slightly different approach which looks a little bit like a Redux state management system.
private loading = false;
private loaded = false;
private config$ = new BehaviorSubject<InitConfig>(null);
public getConfig() {
this.requestConfig();
return this.config$.asObservable();
}
public requestConfig() {
if (loading || loaded) {
return;
}
this.loading = true;
this.http.get('public/config/initconfig')
.pipe(
take(1),
tap((config: InitConfig) => {
this.config$.next(config);
this.loaded = true;
this.loading = false;
}),
).subscribe();
}
It may be worth looking into implementing something like NgRx, as it is built precisely for this kind of more complex state management.
Upvotes: 0
Reputation: 55443
The usual problem with your approach is, whenever you make an HTTP call, its going to give you a new observable. This new observable is by default COLD in nature . It means you have to subscribe to it to get the result. If you call it multiple times, you are gonna get multiple observables and you will end up making multiple HTTP requests.
SOLUTION
You should use a single Observable and subscribe to it multiple times with Share operator. Share operator will make the observable HOT after making the first HTTP call. As soon as it becomes hot, if other subscriber is found, rather than making an other HTTP call, it will simply return the data.
in service,
private publicObservable = this.getConfig(); // publicObservable single observable
private getConfig(): Observable<InitConfig> {
return this.http.get('public/config/initconfig')
.pipe(
share() // share operator
);
}
public getInitConfig(): Observable<InitConfig> {
return this.service.publicObservable;
}
In Demo, I subscribe to same HTTP 8 times. If I remove, share operator, you can see in NETWORK tab, 8 calls will be made
With share operator, only 1 request will be made.
I hope this will help !
Upvotes: 4
Reputation: 1138
You need a private observable that indicates if the config was already fetched, if it wasn't (which means it's the first subscription) initialize it and either way return it.
First, add a private config$: Observable<InitConfig>
to your service.
Next, use shareReplay(1)
in order to share the result among the subscribers.
It should look something like this:
private config$: Observable<InitConfig>;
getInitConfig(): Observable<InitConfig> {
if (!this.config$) {
this.config$ = this.http
.get("https://jsonplaceholder.typicode.com/users/1")
.pipe(
tap(() => console.log("config fetched")),
shareReplay(1)
);
}
return this.config$;
}
You can test it by subscribing multiple times and see config fetched
being logged only once:
ngOnInit(): void {
this.configService.getInitConfig().subscribe(() => console.log('Config Subscription - 1'))
this.configService.getInitConfig().subscribe(() => console.log('Config Subscription - 2'))
this.configService.getInitConfig().subscribe(() => console.log('Config Subscription - 3'))
}
You can see the full code in this stackblitz
Upvotes: 4
Reputation: 6579
You can do this with shareReplay(1)
private getConfig(): Observable<InitConfig> {
return this.http.get('public/config/initconfig')
.pipe(
shareReplay(1)
);
}
Example: https://stackblitz.com/edit/angular-ivy-b2osdk
Upvotes: -1
Reputation: 11951
promises will work exactly as you want. it is the most easy and elegant way
private getConfig(): Promise<InitConfig> {
return this.http.get('public/config/initconfig').toPromise();
}
public getInitConfig(): Promise<InitConfig> {
return this.config = this.config || this.getConfig();
}
Upvotes: 0