rxjs error subscription not being triggered

Viewed 707

I have a .net Core web-api application. I intentionally return an exception from the controller, but I don't see the exception being treated in the error treatment of the subscription. This is my code (copy by hand, so typos might happen): Controller:

public IActionResult getSomeErrorAsTest()
{
    try
    {
        throw new Exception("Serer error");
    }
    catch(Exception ex)
    {
        return StatusCode(StatusCodes.Status500InternalServerError, new List<string>());
        //throw ex;
    }
}

Angular service:

export class MyService
{
    MyDataSubject = new Subject<any[]>();
    MyDataChanged :Observable>any[]> = this.MyDataSubject.asObservable();
    
    subscribe :Subscription;
    constructor(private httpClient : HttpClient)
    {
        this.subscribe = timer(0, 30000).pipe(
        switchMap(()=>
            this.getData())).subscribe();
    }
    getData()
    {
        return this.httpClient.get<any[]>(<controller url>)
        .pipe(
            tap(res =>
            {
                this.MyDataSubject.next(res);
            }),
            catchError(error =>
                {
                    debugger;//I would expect to catch the debugger here, but nothing happens
                    return throwError(error);
                })
            )
    }
}   

Component:

export class MyComponent (private mySrv : MyService)
{
    getMyData()
    {
        let sub =this.mySrv.MyDataChanged.subscribe(result => doSomething(),
                                                    error=> popUpAlert());
    }
}
1 Answers

As @fridoo points out, your component is subscribing to an observable stream that never gets any errors emitted. This is because you have two separate observables.

You don't actually need to maintain a subject and emit manually. You can achieve polling and exposing an observable of changes directly from the getData method.

You may even consider making getData() private and only let consumers use the MyDataChanged observable. (maybe rename to just data$)

export class MyService {
    data$: Observable<any[]> = timer(0, 30000).pipe(
        switchMapTo(this.getData()),
        distinctUntilChanged(compareFn)
    );
    
    constructor(private httpClient: HttpClient) { }

    private getData() {
        return this.httpClient.get(<controller url>).pipe(
            retry(2)
        );
    }

    compareFn(oldVal: any[], newVal: any[]) {
        // add logic to determine if emission
        // is considered to be different.
    }
}   

Notice, you don't have to subscribe in your service. You also don't need to have a separate subject. This has the added benefit of being lazy, as the http calls will not start polling until the data$ observable is actually subscribed to.

You can choose to catch errors in your getData() method or in your component.

Operators used:

  1. switchMapTo - since you aren't using the param passed from the timer, you can use this more concise method instead of the regular switchMap

  2. distinctUntilChanged - will filter out consecutive emissions that are the same. You provide a "compare function" to determine if a value is considered different from the previous.

  3. retry - is handy to retry http calls should an error occur.

Now, in your component:

export class MyComponent {

    constructor(private mySrv: MyService){ }

    getMyData() {
        const sub = this.mySrv.data$.subscribe(
            result => doSomething(),
            error => popUpAlert()
        );
    }
}

I would be remiss if I didn't mention that it's often simpler to leverage the async pipe in your template rather than manage a subscription in your controller.

That case would look something like:

export class MyComponent {

    constructor(private mySrv: MyService){ }

    data$ = this.mySrv.data$.pipe(
        // tap(data => doSomething(data)), <-- hopefully you don't even need this
        catchError(error => { 
            popUpAlert(error);
        })
    );
}

Then the template:

<ul>
    <li *ngFor="let item of data$ | async> {{ item.property }}</li>
</ul>
Related