问题
I have a service call, whose response is cached inside my Angular service like this:
public cacheMyServiceResponse(): Observable<any> {
return this.appConfig.getEndpoint('myService')
.pipe(
switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl())),
tap((body: any) => {
if (body) { // this endpoint can also return a 204 (No Content), where body is null
this.cache = body.myData;
}
}),
take(1),
catchError(error => {
this.errorService.trackError(error.status);
return of(true);
})
);
}
So the response of my http.get call will be cached here, in a global variable called "cache".
The problem is, this call may really response very late, so we wanted to call this endpoint as soon as our page loads (while initialization). But the actual response, or whether our call is finished (either success or error), we need this info only then when user clicks a button.. Of course at that moment of button click, the response may not be there yet, and in this case i would like to wait for it. (so i need more than a simple boolean flag)
So i want to initialize this call in a ngOnInit like this:
ngOnInit() {
this.myService.cacheMyServiceResponse().subscribe();
}
But somewhere else i need to know if it is already finished the call, without firing my http call twice.
onClick() {
this.myService.cacheMyServiceResponse().subscribe(() => {
// call is finished..
});
}
At the moment the service will be called twice. How can i do this?
PS: i dont have an error handling on purpose, i just have to know if the service call finished at all.
回答1:
I suggest to use ReplaySubject() and subscribe to the ReplaySubject() onClick instead, it will wait for your service to emit data while it still can be subscribed to, also if it did not be subscribed before the data from service emit, you wont miss the data:
yourWaitingData = new ReplaySubject();
subscription;
ngOnInit() {
this.myService.cacheMyServiceResponse().subscribe(res => {
//yourWaitingData only emit when res is return from API call
this.yourWaitingData.next(res)
});
}
Then subscribe to it:
onClick() {
if(this.subscription){
this.subscription.unsubscribe()
}
this.subscription = this.yourWaitingData.subscribe((x) => {
// subscribed and will wait for data to be emited from service
console.log(x)
});
}
回答2:
You can user Resolvers here for your scenario . It will call your method when you hit to your route.
Example:
@Injectable()
export class ExampleResolver implements Resolve<any> {
constructor(private apiService: APIService) {}
resolve(route: ActivatedRouteSnapshot) {
return this.apiService.getItems(route.params.date);
}
}
Your Route :
{
path: 'routeName',
component: YourComponent,
resolve: { items: ExampleResolver }
}
回答3:
Use a boolean variable to check the state of the actual response, or whether our call is finished (either success or error); This boolean can then be checked then when user clicks a button... following code to explain what i mean...
callMade:boolean = false;
callFinished:boolean = false;
ngOnInit(){
this.callMade = true;
this.myService.cacheMyServiceResponse().subscribe(
dataa => { /* get and process data */}
,() => { /*this is the finally block */
this.callFinished = true;
}
);
}
someOtherFunction(){
if (this.callMade == true && this.callFinished == false){
/* call the service again */
}
}
回答4:
Why don't you save the Observable ?
public cacheMyServiceResponse(): Observable<any> {
if(this.cache) {
return of(this.cache);
else if(!this.currentCall) {
this.currentCall = this.appConfig.getEndpoint('myService')
.pipe(
switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl())),
tap((body: any) => {
if (body) { // this endpoint can also return a 204 (No Content), where body is null
this.cache = body.myData;
}
}),
take(1),
catchError(error => {
this.errorService.trackError(error.status);
return of(true);
})
);
}
return this.currentCall;
}
回答5:
Use shareReplay to create a simple cache that fires the http request once and provides it's return value to all subsequent subscriptions from cache.
Service
private cache$: Observable<any>;
getData(): Observable<any> {
if (!this.cache$) {
this.cache$ = this.requestData().pipe(shareReplay(1));
}
return this.cache$;
}
private requestData(): Observable<any> {
return this.appConfig.getEndpoint('myService')
.pipe(
switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl())),
catchError(error => {
this.errorService.trackError(error.status);
return of(true);
})
);
}
Component
You can subscribe to this.myService.getData() multiple times without firing multiple http calls. Only the first subscription fires a call.
ngOnInit() {
this.myService.getData().subscribe();
}
onClick() {
this.myService.getData().subscribe(data => console.log('data from click'));
}
https://stackblitz.com/edit/angular-ysafwb
来源:https://stackoverflow.com/questions/57374975/angular-subscribe-multiple-times-without-firing-multiple-calls