DEV Community

Eduard Krivanek
Eduard Krivanek

Posted on • Updated on

Angular Rxjs - CatchError Position Matter!

Using rxjs you have many occurrences when you want to catch the errors from your streams, handle them and return an arbitrary value. Examples may be include http calls, logical operations, calculations, etc. However did you know that the position of you catchError operator does matter ?

Problem Overview

The catchError operator starts to be tricky when you also introduce a higher-order observable such as switchMap. Let’s have an example that you expect the user’s input and based on it you want to fetch some data. Here is the code describing this use-case.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  template: `    
    <section>
      <h2>Write something to the input</h2>  
      <input [formControl]="searchControl" placeholder="write" />
    <section>
  `,
})
export class App implements OnInit {
  searchControl = new FormControl('', { nonNullable: true });

  ngOnInit() {
    this.searchControl.valueChanges
      .pipe(
        switchMap(() =>
          this.mockApiRequest()
        )
      )
      .subscribe();
  }

  /**
   * mock API request with some delay
   */
  private mockApiRequest(): Observable<unknown> {
    return of({}).pipe(
      delay(3000),
      switchMap(() => throwError(
        () => new Error('I have failed you'))
      )
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

The method mockApiRequest() should model a HTTP call to a server, which, ask you see, will fail. Alright, let’s just add catchError you may say, however the question is, on which position should the catchError be added to ?

First Solution

Your initial solution may be adding catchError at the end of the stream as follows:

ngOnInit() {
    this.searchControl.valueChanges
      .pipe(
        filter((x) => x.length > 3)
        switchMap(() =>
          this.mockApiRequest()
        ),
        catchError(() => {
                    // todo: handle the error
          return EMPTY;
        })
      )
      .subscribe();
  }
Enter fullscreen mode Exit fullscreen mode

At the first glance there is no problem with the code. When the mockApiRequest() throws an error, you catch it and handle it. However what if you received this error just for this specific string value sent to the backend and you want to continue listening on the user’s input and sent it to the backend ?

Quite a normal use case, which, in this case, will not work. In this example you put the catchError at the end of the stream and even if the mockApiRequest() throws the error, you still handle it at the end, therefore rxjs will treat it as the whole stream has an error an your stream will stop working.

Look at the below example where we console log each user’s input value, however after receiving an error, the console logs will no longer work.

Image description

As this may not be your desired behaviour and if you still want to keep listening on the user’s input, here is a solution for you.

Second Solution

In this example, to keep still listening of the user’s input, let’s place the catchError operator on the mockApiRequest() , instead of the end.

ngOnInit() {
    this.searchControl.valueChanges
      .pipe(
        filter((x) => x.length > 3)
        switchMap(() =>
          this.mockApiRequest().pipe(
                   catchError(() => {
                            // todo: handle the error
                  return EMPTY;
                })
                    )
        )
      )
      .subscribe();
  }
Enter fullscreen mode Exit fullscreen mode

With this change, you tell rxjs to return an arbitrary value if the API request fails, but not for the whole stream. Here is the result that you achieve by this simple change.

Image description

Summary

This short blogpost describe how the catchError operator can influence the behaviour of your application. You should always catch and handle application errors, but the position of the operator also matters, therefore if you know that your higher-order observables can error out, put the catchError operator on them too, not just at the end of you stream.

If you want to play around with this example visit the stackblitz example connect with me on the following sites:

dev.to | LinkedIn| Personal Website | Github

Top comments (0)