1

Consider I have a Rest API the lists events.

  • POST [203]: gives an array of events.
  • POST [404]: gives no data found.

Here, goes my code.

export function httpFetchRetry<T>(source: Observable<T>): Observable<T> {
  let errorScanner = (errorCount: number, error: any) => {
    if (errorCount >= 5) throw error;
    else if (error.status === 404) throw error;
    else return errorCount + 1;
  };
    
  return source.pipe(retryWhen(e => {
    return e.pipe(scan(errorScanner, 0), delay(3000))
  }));
}
    
export function httpFetchMap<T>(source: Observable<T>): Observable<T[]>{
  return source.pipe(map(({data}: any):any[] => data));
}

private listEvents(user_id: string){

  let url: string = `${environment.apiEndpoint}/event`;
  let payload: any = { user_id };

  this.allEvents$ = this.http.post(url, payload)
    .pipe(httpFetchRetry)
    .pipe(httpFetchMap)
}

The code works just fine with async pipe.

But, now I have to achieve a display of one of these statuses on the UI.

<p> Loading </p>
<p> Loaded </p>
<p> No Data <p>
<p> Error <p>

Q1: If I use n async pipes, n API call(s) are initiated. How do I avoid this?

Q2: How do I one of these statuses?


Currently, I am solving this as

.toPromise()
  .then(data => {
    this.data= data;
    this.noDataFound = false;
  })
  .catch(err => {
    if(err.status === 404){
      this.noDataFound = true;
    }
    else {
      this.snackBar.open("Something went wrong. Please try again.", null , { duration: 2000 });
    }
  })
  .finally(() => { this.pageLoader = false });

1 Answer 1

2

Observables differ from Promises in that they do nothing until subscribed to. You can chain a pipeline of transformations to an Observable, but the original Observer function will not be run until the Observable wrapping them has been subscribed. They also differ from Promises in that the Observer function will run for each subscription to the Observable.

The http methods return a cold, finite Observable with one emission before they are completed. Here the Observer function is the http call itself. Check your dev tools and you'll find that there's no http call made until you subscribe to the Observable.

The async pipe actually subscribes to the Observable under the hood and unwraps its emissions as values, so using the pipe will cause the Observable to be run. Similarly, the toPromise operator also subscribes to the source Observable and converts it to a Promise, so it behaves like a Promise by running immediately.

The async pipe is usually the recommended way to unwrap Observables, but with a finite Observable like an http request, you may want to unwrap the value and hold it somewhere outside of the template:

private listEvents(user_id: string){
    
  let url: string = `${environment.apiEndpoint}/event`;
  let payload: any = { user_id };
    
  this.http.post(url, payload)
    .pipe(httpFetchRetry, httpFetchMap)
    .subscribe(s => this.allEvents = s);
}

A good design pattern is to hold this value in a service for multiple components to access.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.