Angular: Error handling - Interceptors and modals
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
D.B
Updated on June 05, 2022Comments
-
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 almost 6 yearsThis is a good answer. It gives great ideas about how to implement a solution. Thanks.
-
netalex about 5 yearscan't get it to work: got "property show() is not present in type "ModalComponent"