Angular - subscribe multiple times without firing multiple calls?

徘徊边缘 提交于 2019-12-10 23:10:55

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!