3

I'm using Angular (9) powered Bootstrap (6.1.0) TypeAhead and defining its search function like so:

search = (text$: Observable<string>) => {
    return text$.pipe(
      debounceTime(200),
      distinctUntilChanged(),
      // switchMap allows returning an observable rather than maps array
      switchMap((searchText) => {
        if (!searchText || searchText.trim().length == 0) {
          // when the user erases the searchText
          this.dealerRepUserID = 0;
          this.dealerRepChanging.emit(this.dealerRepUserID);
          return EMPTY;
        }
        else if (this.dealerID == this.hostOrganizationID) {
          // get a list of host reps
          return this.myService.getHostRepsAutoComplete(searchText, this.includeInactive);
        } else {
          // get a list of dealer reps
          return this.myService.getDealerReps(this.dealerID, searchText);
        }
      })
    );
  }

The function must return an Observable. How do I catch an error thrown inside the switchMap?

CAK2
  • 1,892
  • 1
  • 15
  • 17
  • It depends what error. You can use `try-catch` on the whole block or you can catch `error` notifications emitted by `this.myService.get*` calls with `catchError()` operator depending on what you want to do. Or you can also put `catchError()` after `switchMap`. – martin Jun 25 '20 at 08:56

2 Answers2

5

The switchMap by itself won't throw any error, the thing that might do something unexpected are the returned observables this.myService.getHostRepsAutoComplete and this.myService.getDealerReps. A tricky moment with the catching errors is that whenever there is an error the observable which is throwing the error is being killed.

For example

observable$.pipe(
 switchMap(() => observable2$),
 catchError(() => doSomethingFunction())
).subscribe()

observable$ will be completed once there is an error, that will complete your search stream and you will get no more data after the error.

As Phat Tran Ky showed in his example the handling of errors should happen inside the new streams in the switchMap operator

observable$.pipe(
 switchMap(() => observable2$.pipe(catchError(() => doSomethingFunction())),
 )
).subscribe()

By doing so whenever there is an error thrown from inside it will kill the inner observable (observable2$) but won't kill the outer subscriptions on the outer observable observable$

A further enhancement that you can do in order to handle your errors in one spot might be to merge the inner observable in one, for example, something like

observable$.pipe(
 switchMap(() => {
   return merge(
   observable1$.pipe(filter(() => ${your if else condition for case 1})),
   observable2$.pipe(filter(() => ${your if else condition for case 2})),
   observable3$.pipe(filter(() => ${your if else condition for case 3})),
   ).pipe(catchError((error) => yourErrorHandlerFunction(error)))
  })),
 )
).subscribe()
4

Have you try the catchError

import { catchError } from 'rxjs/operators';
return text$.pipe(
      debounceTime(200),
      distinctUntilChanged(),
      // switchMap allows returning an observable rather than maps array
      switchMap((searchText) => {
        if (!searchText || searchText.trim().length == 0) {
          // when the user erases the searchText
          this.dealerRepUserID = 0;
          this.dealerRepChanging.emit(this.dealerRepUserID);
          return EMPTY;
        }
        else if (this.dealerID == this.hostOrganizationID) {
          // get a list of host reps
          return this.myService.getHostRepsAutoComplete(searchText, this.includeInactive).pipe(catchError(error => of());
        } else {
          // get a list of dealer reps
          return this.myService.getDealerReps(this.dealerID, searchText).pipe(catchError(error => of());
        }
      })
    );

Here is my app effect

public loadDataPerformance$: Observable<Action> = createEffect(() => {
    return this.actions$.pipe(
      ofType(RiskProfileActions.loadDataPerformance),
      withLatestFrom(
        this.store$.select(fromRoot.getAnalyticsFilterSelectedOptions),
        this.store$.pipe(select(fromFactoryPerformance.getFactoryId))
      ),
      switchMap(([{ recordDate }, filters, factoryId]) =>
        this.riskProfileApiService.getDataPerformanceData(filters, factoryId, recordDate).pipe(
          map((riskDataPerformanceData: PerformanceDataModel) =>
            RiskProfileActions.loadRiskScoreBreakdownPerformanceSuccess(riskDataPerformanceData)
          ),
          catchError(error => of(RiskProfileActions.loadRiskScoreBreakdownPerformanceFail(error)))
        )
      )
    );
  });

Phat Tran
  • 3,404
  • 1
  • 19
  • 22