Search code examples
angularrxjsangular7rxjs6

Nest httpClient calls and tell which one failed?


I have a component somewhat like the pseudo-code snippet at the bottom.

I need to amend onDeployForTesting() so that it calls myService.save() before calling myService.deployForTesting(). I have been looking into nested Observables and nested calls to httpClient but I can never tell which Observable failed (if one does). I need to know this so that I can continue to set notifications.

Question

How do I nest two (or more) httpClient requests, and see which one in the chain failed (in an RxJS way)?

What I have tried

onSaveAndDeployForTesting() {
    this.myService
        .save(arg1)
        .pipe(
            concatMap(  // also tried: switchMap, mergeMap
                () => this.myService.deployForTesting(arg2),
            ),
        )
        .subscribe(
            console.info,
            console.error,  // this gives a very generic error with no pointer to which Observable actually failed
            console.info,
        );
}

Existing code

class MyClass {
    // other code

    onSave() {
        this.myService.save(arg1)
            .subscribe(
                (data) => {
                    this.notificationService.set({
                        type: NotificationType.SUCCESS,
                        title: 'Success',
                        description: 'Lorem ipsum dolor sit amet',
                    });
                },
                (err) => {
                    if (err.errorCode === 409) {
                        this.notificationService.set({
                            type: NotificationType.WARNING,
                            title: 'Save Conflict',
                            description: 'Lorem ipsum dolor sit amet',
                        });
                    } else {
                        this.notificationService.set({
                            type: NotificationType.ERROR,
                            title: 'Error',
                            description: 'Lorem ipsum dolor sit amet',
                        });
                    }
                },
            );
    }

    onDeployForTesting(arg1) {
        this.myService.deployForTesting(arg1)
            .subscribe(
                (data) => {
                    if (data.status === 207) {
                        this.notificationService.set({
                            type: NotificationType.WARNING,
                            title: 'Unable to deploy configuration',
                            description: 'Lorem ipsum dolor sit amet',
                        });
                    } else {
                        this.notificationService.set({
                            type: NotificationType.SUCCESS,
                            title: 'Success',
                            description: 'Lorem ipsum dolor sit amet',
                        });
                    }
                },
                (err) => {
                    this.notificationService.set({
                        type: NotificationType.ERROR,
                        title: 'Error',
                        description: 'Lorem ipsum dolor sit amet',
                    });
                },
            );
    }
}

class MyService {
    // other code

    save(data) {
        return this.http.put('/my/save/url', data);
    }

    deployForTesting(data) {
        return this.http.post('/my/deploy/url', data);
    }
}

Solution

  • You could use RxJS catchError. Try the following

    import { throwError, of, EMPTY } from 'rxjs';
    import { catchError, concatMap } from 'rxjs/operators';
    
    onSaveAndDeployForTesting() {
      this.myService.save(arg1).pipe(
        catchError(saveError => this.handleSaveError(saveError)),
        concatMap(() => this.myService.deployForTesting(arg2).pipe(
          catchError(deployError => this.handleDeployError(deployError))
        ))
      )
      .subscribe(
        console.info,
        console.error,
        console.info,
      );
    }
    
    handleSaveError(error) {
      // handle error from `save()` call
      return EMPTY;      // also `throwError()` or `of()`
    }
    
    handleDeployError(error) {
      // handle error from `deployForTesting()` call
      return EMPTY;      // also `throwError()` or `of()`
    }
    

    Remember to return an observable from the catchError.