13

I have an Angular/JHipster application that makes HTTP calls. I want to do error handling in my Observable subscriptions.

However, when the first time the error handler is invoked, the err object is not a response, it's a raw {} Object. All subsequent error handler invocations have a valid Response.

Why is this happening, and how do I fix it?

MyComponent.ts:

handleClickPost() {
    this.myService.doPost(this.body)
    .subscribe(
        (res: Response) => {
            this.responseOk = true;
        },
        (err: Response) => {
            // This is a HACK!
            // For some reason, the first time this error handler is invoked (after page load),
            // the response body is empty here and serializes to "{}".
            if (JSON.stringify(err) === '{}') {
                // first request always reaches here... WHY?
                this.handleClickPost(); // NB: workaround
                // err.json(); // results in null "method not found" error
            } else {
                // second request always reaches here... WHY?
                // (I want all requests to go here with a valid 'Response' object)
                this.showRequestFailed = true;
                this.errorResponse = err.json();
            }
        }
    );
}

MyService.ts:

doPost(body): Observable<Response> {
    return this.http.post(this.resourceUrl, body);
}

My shoddy workaround is to just call the method again after the first empty-body failure in order to get a typed Response.

NB: This behavior happens with both Angular 2 and Angular 4.

JJ Zabkar
  • 3,792
  • 7
  • 45
  • 65
  • can you post your service method `this.myService.doPost()` I think you are using Observable and not Promise in your service method. – Niladri Sep 08 '17 at 18:44
  • I usually use `any` type with error handler. Also you have to check from server side what response are you getting first time in case of error.If it's an object then check whether that object has the error response. – Niladri Sep 08 '17 at 18:47
  • @Niladri: Ah, you're correct; I am using Observable (pasted MyService.ts). LMK if that helps guide to an answer. – JJ Zabkar Sep 08 '17 at 20:33
  • I got this similar issue for my server side code where i was not returning the err response properly in a json encoded format. So I got `{}` as an empty object. This looks like an issue from your server side api.I use JSON.NET serializer to serialize the string into a JSON response, so it worked. – Niladri Sep 09 '17 at 07:25
  • instead of using `JSON.stringify(err)` in the error handler can you simply use `console.log(err)` to see what is the content? it's supposed to be already serialized into json so you should not have to use `JSON.stringify` again. – Niladri Sep 09 '17 at 07:29
  • also did you use `JSON.stringify(this.body)` before posting it ? – Niladri Sep 09 '17 at 07:34
  • @Niladri: The POSTed body is correct JSON. The return response has a `Content-type` of `application/json` and is valid JSON. – JJ Zabkar Sep 11 '17 at 17:42
  • Nothing guarantees the kind of value that will be thrown. It is perfectly valid for any value to signal an error. If you look at the type of the `error` callback function as declared by subscribe, it takes a value (the error) that is of type `any`. By explicitly _typing_ the argument in the provided callback (by writing `(err: Response) =>` you misleading yourself as to the _type_ of the object. There is no reason to necessarily expect a `Response` object and your client side Http layer may be returning something else. In general, annotating the type of a callback param is a bad practice. – Aluan Haddad Sep 14 '17 at 01:42
  • By implication of my other comment, try to avoid the awful practices promoted by the Angular style guide. They often lead to bugs. Instead, learn to use TypeScript idiomatically by using it outside of Angular or without the Angular style guide. – Aluan Haddad Sep 14 '17 at 01:44

2 Answers2

2

First you have to check few things to make sure your setup is correct.

  1. Check in server side whether you are sending the proper JSON encoded response for the first time request i.e the response is correctly serialized or not.

  2. check if the payload for the POST request this.body is already converted to json string or not if you are posting json data to server . You should use JSON.stringify(this.body) before sending the request if you are sending json data.

  3. you should use map()/catch() in you post() method to actually map the response and catch the actual error.

So your code should look like below

MyService.ts:

doPost(body:any): Observable<Response> {
   let payload = JSON.stringify(body); // Stringify payload if you are using JSON data
   let headers      = new Headers({ 'Content-Type': 'application/json' }); // ... Set content type to JSON for json request
   let options      = new RequestOptions({ headers: headers }); // Create a request option
   return this.http.post(this.resourceUrl, payload,options)
                        .map((res:Response) => res.json()) // ...and calling .json() on the response to return data
                        .catch((error:any) => Observable.throw(error.json().error || 'Server error')); //...errors if any;
}

MyComponent.ts:

handleClickPost() {
    this.myService.doPost(this.body)
    .subscribe(
        (res: any) => {
            this.responseOk = true;
        },
        (err: any) => {
            // Log errors if any
            console.log(err);
           }
        }
    );
}

or you can create a separate error handler for the catch() method like below

private handleError(error: any) {
      let errMsg = (error.message) ? error.message : error.status ? `${error.status} - ${error.statusText}` : 'Server error';
       console.log(errMsg); // log to console instead
        return Observable.throw(errMsg);
    }

and use it this way .catch(this.handleError) in your service class.

Niladri
  • 5,832
  • 2
  • 23
  • 41
  • Sorry, this is basically what I'm already doing: subscribing to the posted `Observable`. I have tried a separate error handling method like `handleError`, but the problem is that the response is not a typed `Response`. – JJ Zabkar Sep 11 '17 at 16:05
  • @JJZabkar have to check in server side what are you returning as response? – Niladri Sep 11 '17 at 17:11
  • The server response is valid, as-expected JSON for all requests. The issue only occurs on the first call. – JJ Zabkar Sep 11 '17 at 17:44
  • So what are u returning in the first call ? can you check that with `console.log(typeof err);` – Niladri Sep 11 '17 at 18:04
  • Within the error handler callback method, I can see `typeof` `object`. – JJ Zabkar Sep 12 '17 at 21:27
  • What response are you returning from server in first call? in my case I was returning a `string` but it was not serialized so i was getting an empty `object` – Niladri Sep 13 '17 at 08:29
  • The response that I can see in Chrome Network panel is well-formed JSON. – JJ Zabkar Sep 13 '17 at 21:01
  • @Niladri can you help me with this one https://stackoverflow.com/questions/46257097/typeerror-cannot-read-property-tigerstart-of-undefined/46257551#46257551 –  Sep 17 '17 at 03:33
1

Seems like an issue of preflight Http OPTIONS request. Did you verify the response type.

If it is this issue you can avoid the preflight request by modifying the content-header as below.

   $http.defaults.headers.post["Content-Type"] = "text/plain"

Read more here: Why am I getting an OPTIONS request instead of a GET request?

Aniruddha Das
  • 20,520
  • 23
  • 96
  • 132
Surender Khairwa
  • 601
  • 4
  • 17
  • 1
    I can confirm that this is *not* a preflight request. On the server side, I can see my `GET` endpoint is invoked and returns the expected error response body. – JJ Zabkar Sep 08 '17 at 20:30