'How i can display a MatSnackBar from a Service with Angular Material?
Im using: Angular V6.1.0, Angular Material V6.4.1
Im trying catch the HTTP errors and show them using a MatSnackBar. I seek to show this in every component of my application (where there is an http request). So as not to do the repetitive code
Otherwise i should repeat the same code in every component for display the MatSnackBar with the errors inserted.
This is my service:
import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
// import { HttpClient, HttpErrorResponse, HttpRequest } from '@angular/common/http';
import { Observable, throwError, of, interval, Subject } from 'rxjs';
import { map, catchError, retryWhen, flatMap } from 'rxjs/operators';
import { url, ErrorNotification } from '../globals';
import { MatSnackBar } from '@angular/material';
import { ErrorNotificationComponent } from '../error-notification/error-notification.component';
@Injectable({
providedIn: 'root'
})
export class XhrErrorHandlerService {
public subj_notification: Subject<string> = new Subject();
constructor(
public snackBar: MatSnackBar
) {
}
public handleError (error: HttpErrorResponse | any) {
this.snackBar.open('Error message: '+error.error.error, 'action', {
duration: 4000,
});
return throwError(error);
}
}
Solution 1:[1]
Create a service with this:
custom-snackbar.service.ts
import { Injectable, NgZone } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
@Injectable()
export class CustomSnackbarService {
constructor(
private snackBar: MatSnackBar,
private zone: NgZone
) {
}
public open(message: string, action = 'success', duration = 4000): void {
this.zone.run(() => {
this.snackBar.open(message, action, { duration });
});
}
}
Add the MatSnackBarModule
to the app.module.ts
:
import { MatSnackBarModule } from '@angular/material/snack-bar';
...
imports: [
BrowserModule,
AppRoutingModule,
MatSnackBarModule,
],
...
Also it needs to be run in ngZone: https://github.com/angular/material2/issues/9875
Then in the error-service.ts
:
public handleError (error: HttpErrorResponse | any) {
customSnackbarService.open(error, 'error')
return throwError(error);
}
Solution 2:[2]
Use fat arrow ()=>
instead of function on the handleError
public handleError = (error: HttpErrorResponse | any) => {
this.snackBar.open('Error message: '+error.error.error, 'action', {
duration: 4000,
});
return throwError(error);
}
Solution 3:[3]
The question is about the default code what you write in the "error" function of any observable to make HTTP request and establish a generic action by default in case of get any error HTTP 4xx response from the API (for my particular case, display a MatSnackBar with the error). Well, i found the solution, ovewriting the ErrorHandle of Angular implementing this: https://angular.io/api/core/ErrorHandler
This is my XhrErrorHandlerService
import { Injectable, ErrorHandler, Injector, NgZone } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { MatSnackBar } from '@angular/material';
@Injectable({
providedIn: 'root'
})
export class XhrErrorHandlerService implements ErrorHandler{
constructor(
private injector: Injector,
public snackBar: MatSnackBar,
private readonly zone: NgZone
) {}
handleError(error: Error | HttpErrorResponse){
if (error instanceof HttpErrorResponse) {
for (var i = 0; i < error.error.length; i++) {
this.zone.run(() => {
const snackBar = this.snackBar.open(error.error[i], error.status + ' OK', {
verticalPosition: 'bottom',
horizontalPosition: 'center',
duration: 3000,
});
snackBar.onAction().subscribe(() => {
snackBar.dismiss();
})
});
}
}
else{
console.error(error);
}
}
}
And this is my ng Module:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ErrorHandler } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import * as moment from 'moment';
import { AppComponent } from './app.component';
//ANIMATIONS
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
//ANGULAR MATERIAL
import { MaterialModule } from './amaterial.module';
//SERVICES
import { AuthService } from './services/auth.service';
import { XhrErrorHandlerService } from './services/xhr-error-handler.service';
//RUTAS
import { RouteRoutingModule } from './route-routing.module';
//INTERCEPTOR
import { AuthInterceptor } from './http-interceptor/auth-interceptor';
@NgModule({
declarations: [
AppComponent,
],
entryComponents: [],
imports: [
BrowserModule,
BrowserAnimationsModule,
MaterialModule,
RouteRoutingModule,
EntryComponentModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule
],
providers: [
AuthService,
XhrErrorHandlerService,
{ provide: ErrorHandler, useClass: XhrErrorHandlerService }
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
],
bootstrap: [AppComponent]
})
export class AppModule { }
Solution 4:[4]
It is possible to open a snackbar in any service. The key is a proper way of using handleError()
function in catchError()
- context of this
must be bound with the handler function.
Add MatSnackBarModule
into app.module.ts
imports array
import { MatSnackBarModule } from '@angular/material/snack-bar';
...
imports: [MatSnackBarModule]
...
Use the snackbar in any service, e.g. MyService
like this:
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ApiService } from './api.service';
@Injectable()
export class MyService {
constructor(
private api: ApiService,
private snackBar: MatSnackBar,
) {}
getRegime(): Observable<Regime> {
return this.api.regime().pipe(
catchError((err) => this.handleError(err)) //bind the context of 'this' instance with '=>'
);
}
getBranches(): Observable<Branch[]> {
return this.api.branches().pipe(
catchError(this.handleError.bind(this)) //bind the context of 'this' instance with 'bind(this)'
);
}
handleError(err: any) {
this.snackBar.open(`Failed.`, 'Ok', { panelClass: 'warn' });
return throwError(err);
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | louiiuol |
Solution 2 | Makhele Sabata |
Solution 3 | |
Solution 4 | Patronaut |