0

Edit [04.01.2020]

I figuered out, that the string that I am getting "Bad Request" is the message of my error object. (Message is not set). So when I am changing the API response so that the message contains my actual error object, it is fine.

let error = {error: {}, code: '', message: {error: {}, code: ''}};
if (err.code === 'DUPLICATE_ENTRY') {
  error.message.code = 'DUPLICATE_ENTRY';
  error.message.error = {
    columns: ["name"]
  };
}

So we could say, that this fixes my problem, but to be honest, thats not what im expecting. Am I missing something or is this the correct way, how this is supposed to work? Am I really supposed to put my actual error object into a pseudo message block?


Technologies

I am using Angular 8 as Frontend and nest.js 6.12.9 with typeorm as API.

Short description

The problem consists in being unable to parse the API response object within Angular. The error in Angular is just a string saying Bad Request

Setup

API Controller

@Post()
async create(@Query() link: string, @Body() enrollment: Enrollment, @Res() res: Response) {
    this.enrollmentService.create(enrollment, link).then(tEntrollment => {
        delete tEntrollment.appointment;
        res.status(HttpStatus.CREATED).json(tEntrollment);
    }).catch((err) => {
        let id = this.makeid(10);
        console.log(`[${(new Date()).toDateString()} ${(new Date()).toTimeString()}] Code: ${id} - ${err}`);

        let error = {error: {}, code: ''};
        if (err.code === 'ER_DUP_ENTRY') {
            error.code = 'DUPLICATE_ENTRY';
            error.error = {
                columns: ["name"]
            };
        } else {
            error.error = {
                undefined: {
                    message: "Some error occurred. Please try again later or contact the support",
                    code: id
                }
            };
        }

        res.status(HttpStatus.BAD_REQUEST).json(error);
    });
}

On posting an object like

{
  "additions": [],
  "driver": null,
  "passenger": { "requirement": 1 },
  "name": "Test",
  "comment": ""
}

the controller does, what it is supposed to do. In this case, the name Test is already in use, so the .catch() block handles the rest, and returns an error response with HttpStatus.BAD_REQUEST and the object

{
    "error": {
        "columns": [
            "name"
        ]
    },
    "code": "DUPLICATE_ENTRY"
}

Note that this object is fetched by posting the call via Postman. So everything works fine here.


Main Problem

Like mentioned, the problem is, that I am unable to get this response within my angular project. The call is made by my terminService and looks as follows.

enroll(enrollment: IEnrollmentModel, appointment: IAppointmentModel): Observable<HttpEvent<IEnrollmentModel>> {
    const url = `${environment.api.url}enrollment?link=${appointment.link}`;
    // const req = new HttpRequest('POST', url, {
    //   data: enrollment,
    //   observe: 'response',
    //   reportProgress: true,
    // });
    // return this.httpClient.request(req);
    return this.httpClient.post<IEnrollmentModel>(url, enrollment, {observe: 'response'});
  }

and the handling of this in my ernollment.component.ts looks like this

this.terminService.enroll(output, this.appointment).subscribe(result => {
        if (result.type === HttpEventType.Response) {
          switch (result.status) {
            case HttpStatus.CREATED:
              this.router.navigate([`enroll`], {queryParams: {val: this.appointment.link}});
              break;
          }
        }
      }, error => {
        console.log(error)
        switch (error.status) {
          case HttpStatus.BAD_REQUEST:
            if (error.code === 'DUPLICATE_ENTRY') {
              error.error.columns.forEach(fColumn => {
                  const uppercaseName = fColumn.charAt(0).toUpperCase() + fColumn.substring(1);
                  const fnName: string = 'get' + uppercaseName;
                  this[fnName]().setErrors({inUse: true});
              }
            });
            break;
        }
      }
    );

The posted object looks exactely like shown previously. Looking into the Chrome dev tools I can see, that the response from this API call is exactely like I expected it to be. {"error":{"columns":["name"]},"code":"DUPLICATE_ENTRY"}. This is actually a good sign, but I am unable to get this response within Angular. When logging the error within error => {} I am just getting a string saying Bad Request. I also tried to log error.status error.body error.error and so on, but this just logs undefined, because error is no object. It is just a string.

What am i missing? How can I properly acces and parse the response, including object and HttpStatus returned by my API?

2
  • Does this answer your question? Angular HttpClient error handling difficult Commented Jan 2, 2020 at 14:47
  • 1
    Unfortunately no. I tried console.log(err.error) and console.log(err.error.message);, but err.erroris still undefined. I also speciefied (err: HttpErrorResponse) => {} instead of err => {} Commented Jan 2, 2020 at 14:58

3 Answers 3

0

The Angular HttpClient seems to mangle the response a bit when trying to parse it as JSON. You can catch the original error on Observable level before the subscription:

yourObservable$.pipe(catchError((error) => console.log(error))).subscribe();
Sign up to request clarification or add additional context in comments.

3 Comments

I can't get it to work. There is always type 'void' is not assignable to type 'observableinput any '. I then also can't call result.type within the subscription, because property 'type' does not exist on type 'unknown'.
Maybe this link here helps you: stackoverflow.com/questions/46849689/…
Unfortunately no. I am aware, that the error body should be available in the error property. But thath's not the case in my example. The error I am trying to access is no object, but a string. Even though the response of the API is accessible within the Chrome dev tools
0

In your enroll method, replace the function return type i.e. replace "HttpEvent" with "HttpResponse". See sample below. Hopefully that fixes it.

enroll(enrollment: IEnrollmentModel, appointment: IAppointmentModel): Observable<HttpResponse<IEnrollmentModel>> {
    const url = `${environment.api.url}enrollment?link=${appointment.link}`;
    return this.httpClient.post<IEnrollmentModel>(url, enrollment, {observe: 'response'});
  }

1 Comment

Unfortunately not. Still the same. But I figured something out, see my edit.
0

So due to my discovery described in my edit, I was able to find my error. The error, unfortunately, lies in no code piece I provided. It lies in a interceptor, checking for missing authentication.

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authenticationService: AuthenticationService) {
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(catchError(err => {
      if (err.status === 401) {
        // auto logout if 401 response returned from api
        this.authenticationService.logout();
        location.reload();
      }

      // const error = err.error.message || err.statusText;     <----- This Line
      // return throwError(err);

      return throwError(err);
    }));
  }
}

On recieving a ErrorResponse, the interceptor checks for the status 401 in order to redirect to the login page. Note the commented lines before the return statement. Right here, the error got parsed and shrinked, which is actually not what I want. Knowing that, I can pass the original error to the throwError function and I will get the expected result in my component.

Idea due to this issue response.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.