Angular: Error handling - Interceptors and modals

10,162

Solution 1

For a global error handler example that uses an HttpInterceptor you need the following.

  • ErrorInterceptor
  • ErrorService
  • CustomErrorModal

The flow is:

app.module => register interceptor.

app.module => regsiter error service as a provider.

app.component => register global error handling that show the error modal.

YourCustomComponent => do not subscribe to the error of the Subject/Observable

Your main app.component will subscribe to any updates from the error service and display them accordingly using the modal ref.

app.module

//other code
const interceptors = [{
    provide: HTTP_INTERCEPTORS,
    useClass: ErrorInterceptor,
    multi: true
}];

const services = [{
    ErrorService
}];

@NgModule({
    //other code
    providers: [
        interceptors,
        services
    ],
    //other code
})

error.service

@Injectable()
export class ErrorService
{
    private errors = new Subject<string[]>();

    constructor() { }

    public addErrors = (errors: string[]): void =>
        this.errors.next(errors);

    public getErrors = () =>
        this.errors.asObservable();
}

error.interceptor

@Injectable()
export class ErrorInterceptor implements HttpInterceptor
{
    constructor(private errorService: ErrorService)
    {         
    }

    intercept(
        request: HttpRequest<any>,
        next: HttpHandler): Observable<HttpEvent<any>>
    {
        return next.handle(request).do(() => { }, (response) =>
        {
            if (response instanceof HttpErrorResponse)
            {                
                if (response.status === 401)
                {
                    return;
                }                

                if (response.status === 400 &&
                    response.error)
                {
                    this.errorService.addErrors(Array.isArray(response.error) ? response.error : [response.error]);
                    return;
                }

                this.errorService.addErrors([`Your generic error message`]);
            }

            return Observable.throw(response);
        });
    }
}

app.component

export class AppComponent implements OnDestroy
{
    private ngUnsubscribe = new Subject();

    @ViewChild('modalError')
    displayErrorRef: ModalComponent; 

    constructor(private errorService: ErrorService)
    {
        this.initializeErrors();
    }    

    ngOnDestroy()
    {
        this.ngUnsubscribe.next();
        this.ngUnsubscribe.complete();
    }

    private initializeErrors()
    {
        this
            .errorService
            .getErrors()
            .pipe(takeUntil(this.ngUnsubscribe))
            .subscribe((errors) =>
            {
                //this.displayErrorRef.error = errors
                this.displayErrorRef.show();
            });
    }   
}

ngUnsubscribe is to automagicly dispose the subscription when your main app.component gets destroyed.

Solution 2

I would recomend to create ResponseInterceptor and a ErrorHandlerService in order to intercept bad responses (500's, etc) and trigger a centralized service that handles errors.

So far i believe the best way to services communicate with components are with Observables. Something similar to this:

// ErrorHandlerService
private errorEvent = new Subject();
public errorEvent$ = this.errorTrigger.asObservable();
public triggerError(error: any): void {
   // manipulate your error here and then:
   this.errroEvent.next(error);
}


//ResponseInterceptor
constructor(private errorHandlerService) {}
enter code here
intercept(...): ... {
   //must check if response is an error to then:
   this.errorHandlerService.triggerError(response);
}


// Your component
fetchData() {
   this.errorHandlerService.errorEvent$.subscribe((error) => {
      // here you have the error to manipulate in your component
   });
   this.anyOtherService.fetchObject().subscribe();
}

hope this helps with what you are looking for

Share:
10,162
D.B
Author by

D.B

Updated on June 05, 2022

Comments

  • D.B
    D.B almost 2 years

    I have built an Angular 5 application which is handling errors on every single call i make. Using the HttpClient i am able to intercept the error that happens after a request to the server has been sent. The error is intercepted on a service method that send the request to the API and then when an error happens it is pushed across to the component in order to display a nice modal with the error message.

    I am want to use interceptors to achieve the same behaviour in order to handle all errors on one single centralised way. But I am not sure if it is possible to communicate with the component from the interceptor class, send the message to it, so it can trigger the modal as it is currently doing, or How can I trigger the modal directly from the interceptor class.

    This is my current logic:

    The component:

      ....
     export class VerificationComponent implements OnInit {
      //Use to call the modal when errors happen
      @ViewChild('modalError') displayErrorRef: ModalComponent; 
    
    //Method send a request to the api using a service instance _myService
    getNextRecord() {
    
        this.errorMesage = "";
        this._myService.getCandidate()
            .subscribe(candidate => {
                this.loadCandidate = candidate;
                this.userName = this.appService.getUser();
            }, error => {                
                this.errorMesage = <any>error.errorMessage;                
                this.displayErrorRef.show();
            });
       }
    
    }
    ....
    

    The service:

    .....
    @Injectable()
    
    export class MyService {
    
      getCandidate(): Observable<ICandidate> {
        return this._http.get(this._getCandidateUrl, this.jwt())
            .map((response: Response) => <ICandidate>response.json())            
            .catch(this.handleError);
       }
    
    
    private handleError(error: Response) {        
        if (error.text())
            return Observable.throw({errorMessage:error.text(),erroStatus: error.status });
        else
            return Observable.throw('You are not authorised to get this resource');
        }
    }
    ....
    

    The template:

     <!-- This is a child component to display the error message on the top of 
     this template -->
      .....
    <app-modal #modalError>
    <div class="app-modal-header">
        Error
    </div>
    <div class="app-modal-body">
        {{errorMesage}}
    </div>
    <div class="app-modal-footer">
        <button type="button" class="btn btn-default" (click)="hideModalError()">Logout</button>
        <button type="button" class="btn btn-default"(click)="tryGetNextRecord()">Try Again</button>
    </div>
    </app-modal>
    ....
    
  • D.B
    D.B almost 6 years
    This is a good answer. It gives great ideas about how to implement a solution. Thanks.
  • netalex
    netalex about 5 years
    can't get it to work: got "property show() is not present in type "ModalComponent"