Which @angular/* package(s) are relevant/related to the feature request?
No response
Description
Angular template event listeners currently forward only synchronous exceptions to ErrorHandler.
For example:
@Component({...})
export class AppComponent {
async onClick(): Promise<void> {
throw new Error('Boom');
}
}
<button (click)="onClick()">Click</button>
The rejection from onClick() is not forwarded to Angular's ErrorHandler. Only synchronous exceptions are handled.
Current implementation:
function executeListenerWithErrorHandling(
lView,
context,
listenerFn,
e,
) {
const prevConsumer = setActiveConsumer(null);
try {
return listenerFn(e) !== false;
} catch (error) {
handleUncaughtError(lView, error);
return false;
} finally {
setActiveConsumer(prevConsumer);
}
}
Only synchronous exceptions are captured. If listenerFn returns a Promise, any later rejection escapes this error handling path.
Motivation
This becomes especially problematic in zoneless applications and Web Components.
A common recommendation is to rely on:
window.onunhandledrejection
provideBrowserGlobalErrorListeners()
However, this is not always acceptable.
For example, when Angular is used inside a Web Component, registering a global unhandledrejection listener causes the component to receive Promise rejections originating from unrelated code running on the host page. This makes error attribution difficult and may result in reporting errors that do not belong to the Angular application.
As a result, developers must manually wrap every async template listener:
onClick(): void {
void this.load().catch(error => {
this.errorHandler.handleError(error);
});
}
or introduce custom helper abstractions for all event handlers.
Proposed solution
If a template listener returns a Promise (or PromiseLike), Angular could attach a rejection handler and forward the error to ErrorHandler.
Example:
const result = listenerFn(e);
if (isPromiseLike(result)) {
result.catch(error => {
handleUncaughtError(lView, error);
});
}
return result !== false;
This would preserve current synchronous behavior while ensuring that asynchronous failures originating from Angular template listeners are reported consistently.
This would not catch intermediate floating Promises in compound template expressions, for example:
(click)="async1(); async2()"
In this case only the result of the last expression can be observed by the listener execution pipeline. This limitation seems acceptable and is consistent with normal JavaScript semantics. Developers would still need to avoid floating Promises inside compound expressions.
The proposed feature is only about handling the Promise returned by the template listener expression itself.
Expected benefit
Template event listeners would behave consistently regardless of whether they fail synchronously or asynchronously:
<button (click)="save()">Save</button>
save() {
throw new Error(); // ErrorHandler
}
async save() {
throw new Error(); // ErrorHandler
}
This would improve ergonomics in zoneless applications and embedded Angular Web Components while avoiding reliance on global unhandledrejection handlers.
Alternatives considered
Global unhandledrejection handling
Applications can register a global unhandledrejection listener or use provideBrowserGlobalErrorListeners().
However, this solution operates at the browser level rather than the Angular application level. In embedded scenarios (for example, Angular running inside a Web Component), the listener may receive Promise rejections originating from unrelated code on the host page.
As a result, applications cannot reliably distinguish between errors produced by Angular template listeners and errors produced by external code.
Manual error handling in every async listener
Developers can explicitly catch and forward errors:
onClick(): void {
void this.save().catch(error => {
this.errorHandler.handleError(error);
});
}
This works but requires boilerplate for every async template listener and is easy to forget. It also creates an inconsistency between synchronous and asynchronous handlers, even though both are executed from Angular-managed template bindings.
Which @angular/* package(s) are relevant/related to the feature request?
No response
Description
Angular template event listeners currently forward only synchronous exceptions to
ErrorHandler.For example:
The rejection from
onClick()is not forwarded to Angular'sErrorHandler. Only synchronous exceptions are handled.Current implementation:
Only synchronous exceptions are captured. If
listenerFnreturns aPromise, any later rejection escapes this error handling path.Motivation
This becomes especially problematic in zoneless applications and Web Components.
A common recommendation is to rely on:
window.onunhandledrejectionprovideBrowserGlobalErrorListeners()However, this is not always acceptable.
For example, when Angular is used inside a Web Component, registering a global
unhandledrejectionlistener causes the component to receive Promise rejections originating from unrelated code running on the host page. This makes error attribution difficult and may result in reporting errors that do not belong to the Angular application.As a result, developers must manually wrap every async template listener:
or introduce custom helper abstractions for all event handlers.
Proposed solution
If a template listener returns a Promise (or PromiseLike), Angular could attach a rejection handler and forward the error to
ErrorHandler.Example:
This would preserve current synchronous behavior while ensuring that asynchronous failures originating from Angular template listeners are reported consistently.
This would not catch intermediate floating Promises in compound template expressions, for example:
In this case only the result of the last expression can be observed by the listener execution pipeline. This limitation seems acceptable and is consistent with normal JavaScript semantics. Developers would still need to avoid floating Promises inside compound expressions.
The proposed feature is only about handling the Promise returned by the template listener expression itself.
Expected benefit
Template event listeners would behave consistently regardless of whether they fail synchronously or asynchronously:
This would improve ergonomics in zoneless applications and embedded Angular Web Components while avoiding reliance on global
unhandledrejectionhandlers.Alternatives considered
Global
unhandledrejectionhandlingApplications can register a global
unhandledrejectionlistener or useprovideBrowserGlobalErrorListeners().However, this solution operates at the browser level rather than the Angular application level. In embedded scenarios (for example, Angular running inside a Web Component), the listener may receive Promise rejections originating from unrelated code on the host page.
As a result, applications cannot reliably distinguish between errors produced by Angular template listeners and errors produced by external code.
Manual error handling in every async listener
Developers can explicitly catch and forward errors:
This works but requires boilerplate for every async template listener and is easy to forget. It also creates an inconsistency between synchronous and asynchronous handlers, even though both are executed from Angular-managed template bindings.