Home > Blockchain >  (Angular) Intercept HttpResponse error and continue the Observable
(Angular) Intercept HttpResponse error and continue the Observable

Time:01-12

I have a dropdown text box that does type-ahead search. When I search for a valid item name (that exists in the DB), the search works fine and returns a list of items in the drop down to select from as I type. But when I search for invalid text, the API returns a 400 error (this is good), then the HttpErrorInterceptor intercepts that response in the catchError() method, and throws up an error popup. I don't want the error popup, I want it to forward the error to the text box logic so I can just display 'No Items Found' in the dropdown.

Text box html (uses Angular's NgbTypeahead):

      <input
        id="searchText"
        type="text"
        [(ngModel)]="selectedItem"
        (selectItem)="onSelectItem($event)"
        formControlName="searchText"
        [ngbTypeahead]="search"
        #instance="ngbTypeahead" />

Text box logic:

  search = (input: Observable<string>) => {
    return input.pipe(
      debounceTime(500),
      distinctUntilChanged(),
      switchMap((text) => text.length < 2 ? this.clearItems() //clearItems() is irrelavant
        : this.itemService.getItemSearchData(text).pipe(
          map(responseObj => {
            const itemList = responseObj.data ? orderBy(responseObj.data, ['itemName'], ['asc']) : [];

            if (itemList.length === 0) {

            // this is what I want it to do when I get the error response
              itemList.push({ itemName: 'No Items Found' } as ItemList);
            }
            return itemList;
          })
        )));
  }

// This is in the ItemService class.
  getItemSearchData(searchTerm: string): Observable<any> {
    const searchItem = {
      "filterBy": {
        "key": "itemname",
        "value": searchTerm
      }
    }
 
    return this.http.post(this.itemApiUrl, searchItem, { headers: this.headers });
  }

This is the interceptor:

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
    constructor(private matDialog: MatDialog) { }
 
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .pipe(
        catchError((error: HttpErrorResponse) => {
            let errorMessage = 'Unknown error!';
            if (error.error instanceof ErrorEvent) {
                errorMessage = `Error: ${error.error.message}`;
            } else {
                errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
            }
            
            // the error popup. I DON'T want to throw this when I get the 404 response.
            this.matDialog.open(PopupComponent, {
                data: { actionDesctiption: errorMessage, isError: true },
                panelClass: 'custom-dialog-container'
            });
 
            return throwError(error);
        })
      );
  }

I have tried this: Angular: intercept HTTP errors and continue chain, but the top solutions's return of(new HttpResponse...; statement gave me the error Type 'Observable<unknown>' is not assignable to type 'Observable<HttpEvent<any>>'. I also tried returning next.handle(request) and new Observable<HttpEvent<any>>().

When I place a breakpoint at the map(responseObj => line, it always says "responseObj is not defined".

How can I get the dropdown to show 'No Items Found' when the API returns the 400 error?

CodePudding user response:

It is not clear what is the structure of the data returning from your API. Assuming the API returns data in this format: { itemName: string }[] (i.e. an array of { itemName: string } objects, you can use the http interceptor to check for a 404 error and then alter the response like this:

import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { HttpErrorResponse } from '@angular/common/http';
import { of, throwError } from 'rxjs';

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
    constructor(private matDialog: MatDialog) { }
 
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .pipe(
        catchError((error) => {
            let errorMessage = 'Unknown error!';
            if (error.error instanceof ErrorEvent) {
                errorMessage = `Error: ${error.error.message}`;
            } else {
                errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
            }
            
            // check for a 404 error response
            if (error instanceof HttpErrorResponse && error.status === 404) {
                return this.returnCustomData([{ itemName: 'No Items Found' }]); // returns a response, and doesn't throw the error
            }
            
            // the error popup. I DON'T want to throw this when I get the 404 response.
            this.matDialog.open(PopupComponent, {
                data: { actionDesctiption: errorMessage, isError: true },
                panelClass: 'custom-dialog-container'
            });

            return throwError(error);
        })
      );
    }

    private returnCustomData(body) {
        return of(new HttpResponse({ status: 200, body }));
    }

}

Note: Again, I'm assuming your API returns an array of { itemName: string } objects, this is why I used one object inside an array when called returnCustomData. Remember to change the data object sent to returnCustomData to match the actual data format returned by your API, as if it only returned one result, containing the words 'No Items Found'.

CodePudding user response:

I know your interceptor in handling all the HTTP errors for any request, but, since you need that message error in your component, how about you also add a catchError inside your service pipe?

search = (input: Observable<string>) => {
    return input.pipe(
      debounceTime(500),
      distinctUntilChanged(),
      switchMap((text) => text.length < 2 ? this.clearItems() 
        : this.itemService.getItemSearchData(text)
          .pipe(
             map(responseObj => {
               const itemList = responseObj.data ? orderBy(responseObj.data, ['itemName'], ['asc']) : [];
               return itemList;
             }),
             catchError(() => {
               itemList.push({ itemName: 'No Items Found' } as ItemList));
               of('');
             }
        )));
  }
  •  Tags:  
  • Related