2

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());
    }
}
Guy E
  • 1,775
  • 2
  • 27
  • 55
  • Are you saying `catchError` is never executed? I can see why the error callback in the subscription is never executed, but `catchError` should still be executed once. – frido Aug 20 '20 at 09:44
  • @fridoo - No, catchError is never executed. I can see error 500 message in my console (developers tools) and a "You provided an invalid object where a stream was expected..." error message. How can you see that the error callback in the subscription is never executed - is there a problem in the way it is written ? – Guy E Aug 20 '20 at 11:19
  • 1
    In your component you subscribe to `this.mySrv.MyDataChanged`. This observable never errors because you only push values into it (`this.MyDataSubject.next(res)`). It would error if you called `this.MyDataSubject.error(..)` at some point. But I'm not sure if you want to throw an error on this stream. If an observable errors it terminates and can't emit anything else afterwards. It seems you want the observable to never terminate as you use an endless timer as a trigger. – frido Aug 20 '20 at 16:39

1 Answers1

1

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>
BizzyBob
  • 12,309
  • 4
  • 27
  • 51
  • Thanks for the thorough and detailed answer - I trully appreciate the effort. I'll try it. Is the distinctUntilChanged function is the one that gets emitted when the error occurs ? – Guy E Aug 23 '20 at 05:35
  • `distinctUntilChanged` has nothing to do with errors. It will simply prevent duplicate data from being emitted. So, when you make your http call, and it returns the same results twice in a row, `data$` will not emit if the data hasn't changed since the last emissions. You would still use that `catchError()` operator either in the service or in the component. – BizzyBob Aug 23 '20 at 06:52
  • Regarding the distinctUntilChanged - I'm already using this logic - emitting on different data. I can't find any real difference between what you wrote and what I wrote regarding the catchError. I will try to imitate your solution, but where is the real difference ? – Guy E Aug 23 '20 at 07:42
  • The difference is that you are using two different streams of data. Your component is subscribing to `MyDataChanged`. However, your errors would be emitted through `service.getData()`. So, consumers of `MyDataChanged` will never be able to catch errors, because errors will never get emitted through that stream. – BizzyBob Aug 25 '20 at 12:30
  • As far as the `catchError` inside your `getData()` method: I would expect that to receive errors from the httpClient.get() call. I'm not sure what that wouldn't be happening. Have you tried adding a `console.log()` in that block? – BizzyBob Aug 25 '20 at 12:32
  • 1
    After lots of testing and readings, I figured out that the initial problem was existence of HTTP_INTERCEPTORS which probably "fish" the error responses, and avoided from bubbling up to the client. Except for this, every thing looks OK. I hope that this add some value to you all, too. Thanks for your efforts. – Guy E Aug 26 '20 at 07:32