0

I am making request to the server and want to show translated error message in case of failure.

I read that passing anonymous error function to subscribe is deprecated.

I come up with this code:

this.myService.editSendReport(params)
.pipe(catchError(() => this.translateService.get('settings.report.error')))
.subscribe((res: MyResponce) => {
            // In case of error, we show snack bar with error message.
            if (typeof res === 'string' || res instanceof String) {
                this.snackBar.open(String(res), undefined, {
                    duration: 3000
                });
                return;
            }

            // Success logic
            ...
        });  

But still I find this code kind of weird, because now we must handle showing error message in subscribe in next callback. What is the best approach of showing error message now?

this.translateService.get('settings.report.error') is also Observable. So if I put inside of error of Observer. I will need to subscribe inside of subscribe, which is considered an anti-pattern too.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Anna Melashkina
  • 422
  • 2
  • 13
  • The linked article shows you how to do it, in the final example - you pass an observer object, instead of two separate callback functions. – jonrsharpe Jul 19 '23 at 13:01
  • Then it's up to you how you want to represent it. I'd consider the translated error message to _still_ be a failing observable state, so throwError. But that's a different question to the deprecated subscriber interface (as how to handle that error, as covered in the linked docs, is entirely separate to whether or not you consider it one). But "best" is going to be a matter of opinion without objective criteria. – jonrsharpe Jul 19 '23 at 13:27
  • Have you ever used ErrorInterceptors before? – Wahab Shah Jul 19 '23 at 13:44

2 Answers2

0

You could pass in an object specifying the next and error callbacks.

this.myService.editSendReport(params)
  .pipe(catchError(() => this.translateService.get('settings.report.error')))
  .subscribe({
    next: (res: MyResponce) => {
      // handle success
    },
    error: (error: ErrorType) => {
      if (typeof res === 'string' || res instanceof String) {
        this.snackBar.open(String(res), undefined, {
          duration: 3000
        });
      }
    }
  });

But given that the this.translateService.get('settings.report.error') call corresponds to an observable, I expect the emissions to nevertheless trigger next and not the error callback.

In this case, I'd say it's better to forgo the error callback and perform the action as a side-effect using the tap operator.

```typescript this.myService.editSendReport(params) .pipe( catchError(() => this.translateService.get('settings.report.error').pipe( tap((value) => { this.snackBar.open(String(value), undefined, { duration: 3000 }); }) ) ) ) .subscribe({ next: (res: MyResponce) => { // handle success } }); ```

Edit: The above solution wouldn't work as intended as the next callback would be triggered regardless.

Possible solutions that I could think of immediately:

  1. Continue using the current implementation: only the next callback.
  2. Pipe in switchMap + throwError to the inner observable from catchError with the error callback. And handle the error in error callback.
  3. Pipe in switchMap + return EMPTY to the inner observable from catchError without the error callback. And handle the error within the switchMap callback.
ruth
  • 29,535
  • 4
  • 30
  • 57
  • Yes, that's a problem, that it triggers `next` and not `error` callback. It would be nice, if we somehow could trigger `error` callback in this case. – Anna Melashkina Jul 19 '23 at 13:13
  • @̶A̶n̶n̶a̶M̶e̶l̶a̶s̶h̶k̶i̶n̶a̶:̶ ̶T̶h̶e̶r̶e̶ ̶i̶s̶ ̶t̶h̶e̶ ̶_̶u̶g̶l̶y̶_̶ ̶w̶a̶y̶ ̶u̶s̶i̶n̶g̶ ̶`̶s̶w̶i̶t̶c̶h̶M̶a̶p̶`̶ ̶+̶ ̶`̶t̶h̶r̶o̶w̶E̶r̶r̶o̶r̶`̶ ̶t̶o̶ ̶e̶m̶i̶t̶ ̶a̶n̶y̶t̶h̶i̶n̶g̶ ̶a̶s̶ ̶a̶n̶ ̶e̶r̶r̶o̶r̶.̶ ̶B̶u̶t̶ ̶I̶M̶O̶,̶ ̶t̶o̶ ̶p̶e̶r̶f̶o̶r̶m̶ ̶a̶n̶ ̶a̶c̶t̶i̶o̶n̶ ̶l̶i̶k̶e̶ ̶o̶p̶e̶n̶i̶n̶g̶ ̶a̶ ̶s̶n̶a̶c̶k̶-̶b̶a̶r̶,̶ ̶u̶s̶i̶n̶g̶ ̶t̶h̶e̶ ̶`̶t̶a̶p̶`̶ ̶o̶p̶e̶r̶a̶t̶o̶r̶ ̶i̶s̶ ̶f̶i̶n̶e̶.̶ – ruth Jul 19 '23 at 13:23
  • Sorry just noticed that the `next` callback would be triggered regardless. You might have to either replace the `tap` with `switchMap` + `return EMPTY` **without** the `error` callback. Or `switchMap` + `throwError` **with** the `error` callback. – ruth Jul 19 '23 at 13:27
0

The way I handle errors is to use RxJS throwError like this.

this.http
  .get('www.blah.com/api/v1/fake')
  .pipe(
    catchError((err) => {
      //Handle the error however you like
      alert(err.message);
      return throwError(() => err);
    })
  )
  .subscribe((resp) => {
    alert('I never get called!');
  });

This is described in this guide here - scroll to "The Catch and Rethrow Strategy". Note that the block in subscribe won't get called in this case. I think it's also important to be aware that any error handling you've implemented in HTTP Interceptors will be executed before the error handling shown here.

Here's a stackblitz demonstrating everything

spots
  • 2,483
  • 5
  • 23
  • 38