Search code examples
angularasp.net-core-webapi.net-6.0

Angular - Extract response which is ProblemDetails type


In .NET Web API when any DataAnnotation attribute validation failed, it returns HTTP status code: 400 with the response as below:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-57b63f3ea418de93f36e991b5d8d2567-29a28274787dd62d-00",
  "errors": {
    "": [
      "A non-empty request body is required."
    ],
    "request": [
      "The request field is required."
    ]
  }
}

This is a ProblemDetails class as per MSDN.

Can someone let me know how to parse the errors part into an array of strings?

For example:

errors contain an array, whose item is a key-value pair.

{
  "errors": {
  "Field 1 Name": [
    "The request field is required."
  ],
  "Field 2 Name": [
    "The request field is required."
  ],
  .
  .
  .
  "Field nth Name": [
    "The request field is required."
  ]
}

How can I get these errors into the collection to show on UI for users? I want to show it in toast as:

"Key : Value"

"Field 1 Name : The request field is required."


Solution

  • Use the catchError rxjs operator to handle the response with the error status code.

    Based on your sample API response, I extract the value from the error part, and convert it from the key-value pair (which is key: string, value: string[]), flatten the value to an array of objects with { field: string, error: string } type.

    And return the error Observable as { status: number, errorText: string, response: any } type.

    Service

    import { catchError, throwError } from 'rxjs';
    
    submit(body: any): Observable<any> {
      // In real environment this is how you post request, get response and handle error response
        
      return this.http
        .post(API_URL, body)
        .pipe(catchError((err) => this.handleError(err)));
    }
    
    handleError(err: any): Observable<any> {
      let handleErrResponse = {
        status: err.status,
        errorText: err.message || err.errorMessage || err.title,
        response: err,
      };
    
      if (err.status == 400) {
        let errors = Object.entries(err.errors).reduce(
          (acc, cur: [string, string[]]) => {
            for (let err of cur[1]) {
              acc.push({
                field: cur[0],
                error: err,
              });
            }
    
            return acc;
          },
          [] as { field: string; error: string }[]
        );
    
        handleErrResponse.response = errors;
      }
    
      return throwError(() => handleErrResponse);
    }
    

    When the Observable is returned, trace whether the status is 400. If true, then you pass the formatted response to display the error messages as toast.

    Component

    onSubmit() {
      let body = {};
      this.service.submit(body).subscribe({
        next: (response) => {
          // Success
        },
        error: (err) => {
          if (err.status == 400) {
            this.hasError = true;
    
            this.errors = err.response;
            // Use err.response to display in toast
          }
        },
      });
    }
    

    In my demo, I just display the formatted error response as an HTML template to prove the concept works.

    <div *ngIf="hasError">
      <div *ngFor="let err of errors">
        {{ err.field }} : {{ err.error }}
      </div>
    </div>
    

    Demo @ StackBlitz