Handling Errors with toSignal in Angular

When working with Angular, the toSignal
function is a powerful tool for converting an Observable into a reactive signal. However, there's a nuanced behavior you need to be aware of, especially when dealing with errors emitted by the Observable.
By default, when the Observable associated with a signal emits an error, Angular’s toSignal
will propagate this error every time you attempt to read the signal’s value. We can see this behaviour in the source code:
return computed(
() => {
const current = state();
switch (current.kind) {
case StateKind.Value:
return current.value;
case StateKind.Error:
// 👇👇👇
throw current.error; // Throws the error
}
}
);
This approach has significant implications. If your application has a global error handler, it will be invoked each time the signal’s value is accessed after an error has been emitted. This behavior might not align with your expectations, especially if you’re accustomed to handling errors in the way the async
pipe does.
To better control error handling and mimic the behavior of Angular’s async
pipe, you can use the rejectErrors
option. When this option is enabled, errors from the Observable's error channel are thrown back to RxJS, where they are processed as uncaught exceptions. Here's what this looks like in practice:
const sub = source.subscribe({
next: (value) => state.set({ kind: StateKind.Value, value }),
error: (error) => {
// 👇👇👇
if (options?.rejectErrors) {
throw error;
}
state.set({ kind: StateKind.Error, error });
},
});
By setting rejectErrors
to true
, the signal created by toSignal
will maintain the last successfully emitted value indefinitely. This is because Observables that emit an error do not produce further values. Essentially, the signal will "freeze" at the last good value, ignoring any subsequent errors, which can be particularly useful in scenarios where maintaining application stability is a priority.
Follow me on Medium or Twitter to read more about Angular and JS!