I created this library to provide a way to handle all errors that can happen in an angular application.
Before the introduction of signals, I was using an ErrorHandler and everything worked.
Now, the issue raises when we use signals since they memoize the errors.
If an error is generated when the value of a signal is evaluated, this error is thrown everytime the signal is read.
References:
- https://medium.com/netanelbasal/handling-errors-with-tosignal-in-angular-6500511c0d6f
- angular/angular#51949
It will automatically non-handled handle rxjs, signal and classic errors and make them available in a rx subject (ErrorBus)
- log these errors in the console (useful during development).
- display these errors to the user.
The components in the library are:
Sensor -> Centralized Service -> Notification
- Sensor is meant to capture the errors.
The sensors handle the errors. The default configuration sends the error to the Centralized Service.
The library provides 3 sensors :- ErrorHandler
- Signal
- Http Interceptor (not really interesting, the errorHandler does the same stuff).
- Centralized service
The centralized service is theErrorBuscomponent.
It is basically a rxjsSubjectwrapper.
Any component can listen to the errors that are sent to the ErrorBus. - Notification
This component is meant to report the error to the end user.
The library doesn't provide any implementation for it, since I think every application will have its own need. The implementation will be straightforward :- listen to the ErrorBus
- diplay the error in a toaster or similar component.
If you want to use all the stuff, you'll need to :
- register the providers from
provideErrorHandler()in your app.config. - provide you ErrorNotifier
.app.config.ts
import {
ApplicationConfig,
provideZoneChangeDetection,
} from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideErrorHandler } from '@gonzal/ngx-error-handling';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideErrorHandler(),
provideHttpClient(withInterceptors([errorHttpInterceptor])),
// instantiates your ErrorNotifier at startup
provideAppInitializer(() => {
inject(ErrorNotifier);
})
],
};.ErrorNotifier
import { inject, Injectable } from "@angular/core";
import { ErrorBus } from '@gonzal/ngx-error-handling';
@Injectable({providedIn: 'root'})
export class ErrorNotifier {
#errorBus = inject(ErrorBus);
constructor() {
this.#errorBus.getErrorStream().subscribe((error) => this.#notify(error));
}
#notify(error: Error) {
// or whatever other error reporting mechanism you want to use
console.log(error);
}
}You can also use only the captor you need (i.e. signal captor).
Moreon this in the captor section.
To capture the errors that can be thrown in a signal, you'll need to use safeSignal() method.
This will replace any occurence of computed() or toSignal().
Sample usage :
.Safe signal evaluation
value = input('');
computedValue = safeSignal(() => {
const value = this.value();
return this.#businessLogic(value);
},
{
// value that will be returned by the signal if an error is thrown
fallback: 'default value',
// error handling mechanism
onError: (e) => console.error(e);
}
);.Safe observable to signal conversion
#userService = inject(UserService);
users = safeSignal(this.#userService.getUsers(),
{
// value that will be returned by the signal if an error is thrown
fallback: [],
// error handling mechanism
onError: (e) => console.error(e);
}
);The options parameter is optional. If the parameter is undefined, safeSignal will just use the options from the provider SignalErrorConfiguration
if it is available (if you use provideErrorHandler(), this configuration class is already provided and configured to send the errors to the ErrorBus).
So a more realistic sample would be :
value = input('');
computedValue = safeSignal(() => {
const value = this.value();
return this.#businessLogic(value);
});.Safe observable to signal conversion
#userService = inject(UserService);
users = safeSignal(this.#userService.getUsers(), { fallback: [] });ErrorHttpInterceptor catches all errors thrown by httpClient.
It the ask the ErrorHttpInterceptorConfiguration provider if it should handle the error.
If yes, it call the onError of the ErrorHttpInterceptorConfiguration, marks the error as handled and just rethrows the error.
If ErrorHttpInterceptorConfiguration is not provided, ErrorHttpInterceptor is just a noop interceptor.
Note that provideErrorHandler() already provides ErrorHttpInterceptorConfiguration.
NgxErrorHandler just propagates any error to the ErrorBus.
This is a wrapper around a rxjs Subject.
To use it:
import { inject, Injectable } from "@angular/core";
import { ErrorBus } from '@gonzal/ngx-error-handling';
@Injectable({providedIn: 'root'})
export class ErrorNotifier {
#errorBus = inject(ErrorBus);
constructor() {
this.#errorBus.getErrorStream().subscribe((error) => this.#notify(error));
}
#notify(error: Error) {
// or whatever other error reporting mechanism you want to use
console.log(error);
}
}