angularangular-httpangular-errorhandler

Angular, Http Error blocks any further requests


After an error-response from the server my app does not send any further requests.

I'm sending a request with:

getMachineRules(rules: Array<string>): Observable<RulesResults> {
    return this.http.post<RulesResults>('/rules', { nodes: rules })
      .pipe(
        catchError(this.handleError)
      );
  }

My errorHandler:

  handleError(error) {
    if (error.error instanceof ErrorEvent) {
      console.error('An error occurred:', error.error.message);
    } else {
      console.error(
        `Backend returned code ${error.status}, ` +
        `body was: ${JSON.stringify(error.error)}`);
    }
    return throwError('Something bad happened; please try again later.');
  }

And my pipeline looks like this:

  ngAfterViewInit() {
    this.rulesChanged
      .pipe(
        startWith({}),
        switchMap(() => {
          this.isLoadingResults = true;
          return this.settings.getMachineRules(this.currentRules);
        }),
        map(data => {
          this.isLoadingResults = false;
          this.resultsLength = data.inputs?.length || 0;
          return data;
        }),
        catchError(e => {
          console.log("Error caught");
          this.isLoadingResults = false;
          this.resultsLength = 0;
          return observableOf({ inputs: [] } as RulesResults);
        })
      ).subscribe(
        data => { return this.updateRules(data); },
      );
  }

I can see the "Error caught" message in the console and the method updateRules() seems to work properly even in the error case.

However after a 404-Error-Response the ngAfterViewInit() method is not called anymore. The UI steel reacts on interactions.


Solution

  • Once an Obsevable is failed there is nothing you could do to make it active again, catchError is used to handle errors by returning a new observable or throwing an error. observableOf({ inputs: [] } as RulesResults) is served as substitute in case the main stream fails, meaning your main Observable will never emit again.

    Consider using retry at the end of your pipeline, or optionally have your catchError return the source Observable itself, as demonstrated below:

    catchError((error, source) => {
     ....
      return source;
    })
    

    Alternately put the catchError operator inside a switchMap so that only the inner stream will fail while the the outer (main) stream will remain active, as below:

    ngAfterViewInit() {
      this.rulesChanged
        .pipe(
          startWith({}),
          switchMap(() => {
            this.isLoadingResults = true;
            return this.settings.getMachineRules(this.currentRules).pipe(
              map(data => {
                this.isLoadingResults = false;
                this.resultsLength = data.inputs?.length || 0;
                return data;
              }),
              catchError(e => {
                console.log("Error caught");
                this.isLoadingResults = false;
                this.resultsLength = 0;
                return observableOf({ inputs: [] } as RulesResults);
              })
            )
          })
        ).subscribe(
          data => { return this.updateRules(data); },
        );