I am loading JSON with a service using HTTP GET and use the passed observable with async pipe to display data in my template. An issue with this method is that the HTTP GET is executed and the JSON is downloaded each time I reference the observable in the template - I can verify this using my browser's DevTool/Network window.
Example service logic:
foo$ :Observable<T> = this.myFunction();
myFunction() {
return this.http.get<T>('...').pipe(...);
}
Example component logic:
constructor(public myService :MyService) { }
settings$ :Observable<T> = this.myService.foo$;
Example component template:
<input type="text" name="foo" value="{{(this.bar$ | async)?.foo}}"/>
I think a good workaround would be to create a similar service, which receives the observable from the initial service using HTTP GET and passes the new observable into the templates instead - this way, the HTTP GET itself would be executed once and the additional service would serve data to the template each time it is referenced.
Considering the structure of Angular I wonder if this is the right solution considering logic, readability and efficiency. Could someone verify this or provide a better solution please?
CodePudding user response:
I think you should turn your code into one that would use benefits of shareReplay operator.
In service that currently provides 'myFunction' you need to define an object that would be doing the cache.
private cache$: Observable<T>
Content of your current function should stay as it is, but I would turn it into private one. And then for one that would be visible to the client you would need something like this:
myNewFunction$(): Observable<T> {
if (!this.cache$) {
this.cache$ = this.myFunction().pipe(shareReplay())
}
return this.cache$
}
CodePudding user response:
I think this is a good solution if you want to keep cache in the service.
https://indepth.dev/posts/1248/fastest-way-to-cache-for-lazy-developers-angular-with-rxjs
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, publishReplay, refCount } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class Service<T> {
foo$: Observable<T>;
constructor(private httpClient: HttpClient) { }
myFunction(): Observable<T> {
// Cache it once if foo value is false
if (!this.foo$) {
this.foo$ = this.httpClient.get<T>(``).pipe(
publishReplay(1), // this tells Rx to cache the latest emitted
refCount() // and this tells Rx to keep the Observable alive as long as there are any Subscribers
);
}
return this.foo$;
}
}
If you want to cache data in a component, you can do it as follows.
<ng-container *ngIf="foo$ | async as data">
<input type="text" [value]="data.bar">
<app-child [data]="data"></app-child>
</ng-container>