Reputation: 5574
I'm trying to handle errors in angular globally using an ErrorHandler as layed out here: https://medium.com/@aleixsuau/error-handling-angular-859d529fa53a
I'm forwarding the error messages to a notification service. The app component template is bound to an observable provided by the service using async pipe.
When a client error is thrown, everything works as expected: The error is caught, notification is sent and the UI is displaying the error message. After 3 seconds the message disappears, since the observable changes to a null value.
On HttpErrorResponses the behavior is strange: The error is caught, notification is sent, but the UI does not update. Unless another HttpErrorResponse is thrown within 3 seconds!
Am i missing something or is this a bug in Angular 6 or RxJs?
I created a minimal, complete and verifiable example on stackblitz: https://stackblitz.com/edit/angular-e9keuw
The ErrorHandler:
@Injectable()
export class ErrorSink implements ErrorHandler {
// ErrorHandler is created before the providers
// we have to use the Injector to get them
constructor(private injector: Injector) {}
handleError(error: Error | HttpErrorResponse) {
console.error('Caught error: ', error);
const notificationService = this.injector.get(NotificationService);
// client error
if (!(error instanceof HttpErrorResponse)) {
console.log('client error!');
return notificationService.notify(error.message);
}
// offline error
if (!navigator.onLine) {
console.log('No Internet Connection');
return notificationService.notify('No Internet Connection');
}
// http error
console.log(error.status, error.message);
return notificationService.notify(`${error.status} - ${error.message}`);
}
}
The NotificationService:
@Injectable()
export class NotificationService {
private subject: BehaviorSubject<string> = new BehaviorSubject(null);
readonly notification$: Observable<string> = this.subject.asObservable();
constructor() {}
notify(message) {
console.log('notification', message)
this.subject.next(message);
setTimeout(() => this.subject.next(null), 3000);
}
}
The Component:
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
constructor(
private notificationService: NotificationService,
private http: HttpClient
) {}
throwError(): void {
throw new Error('an error was thrown!');
}
loadUrl() {
this.http.get('https://www.google.com/').subscribe(
data => console.log(data)
);
}
}
And the bound template:
<div *ngIf="notificationService.notification$ | async as notification">
{{ notification }}
</div>
Upvotes: 1
Views: 815
Reputation: 71891
The reason is that the error is being triggered outside the zone. I do not know the exact reason why that happens, because I don't see all your code, but it is :). Update your NotificationService to run the notify inside the zone:
@Injectable()
export class NotificationService {
private subject: BehaviorSubject<string> = new BehaviorSubject(null);
readonly notification$: Observable<string> = this.subject.asObservable();
private timeout: number = 0;
constructor(readonly zone: NgZone) {}
notify(message) {
console.log('notification', message)
clearTimeout(this.timeout);
this.zone.run(() => {
this.subject.next(message);
this.timeout = setTimeout(() => this.subject.next(null), 3000);
});
}
}
One hint though, save your setTimeout ref in a class member. This way you can cancel the setTimeout if you have two errors within 3 seconds. It could happen that the second error cannot be read because it's already set to null
Upvotes: 5