3

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?

2 Answers 2

1

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'.

Sign up to request clarification or add additional context in comments.

4 Comments

I don't have my work laptop on me right now but won't Intellisense give me the same error Type 'Observable<unknown>' is not assignable to type 'Observable<HttpEvent<any>>' that I mentioned in the description? And if I place a breakpoint on map(responseObj => , I should see the array [{ itemName: 'No Items Found' }] in responseObj, yes?
No, using the solution I wrote shouldn't make Intellisense complain that Type 'Observable<unknown>' is not assignable to type 'Observable<HttpEvent<any>>'. Tested at my end. Make sure not to force function return type or casting as in the link to the answer you provided.
Regarding the breakpoint on map(responseObj => , I think you should carefully inspect your switchMap code and see that you didn't miss something there (you wrote that clearItems() is irrelavant.. I don't understand why). Try to comment lines of your code bit by bit to remove complex logic, and then add it back when you're sure that you're getting the response you are expecting from getItemSearchData. The solution I provided you makes getItemSearchData return [{ itemName: 'No Items Found' }] whenever you send to it a phrase that returned 404 api error.
Thanks, this worked.
0

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('');
             }
        )));
  }

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.