skip to content

Global Error Handler

Replacing the ErrorHandler class, forwarding window errors with provideBrowserGlobalErrorListeners, and @boundary/@error blocks. Interviewers probe which errors reach the handler and which never do.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Angular, what does the default ErrorHandler do with an uncaught error, and how do you replace it with your own class?

level: juniorimportance: must knowfreq 55%

answer

  1. one handler for the whole app
  2. default just logs to the console
  3. handleError(error) is the hook
  4. provide it in ApplicationConfig
  5. report, do not recover

basics

~10 s

Angular's default ErrorHandler only logs the error with console.error. To replace it, write a class with a handleError(error) method and register { provide: ErrorHandler, useClass: MyHandler } in the application's providers.

solid answer

~40 s

`ErrorHandler` from `@angular/core` is the single application-wide hook Angular calls for errors it catches itself — errors thrown while it runs your constructors, lifecycle hooks, template bindings and event listeners. The default implementation does nothing but `console.error('ERROR', error)`, so the app keeps running and nothing is reported anywhere. To change that, you write a class that implements `ErrorHandler` with a `handleError(error)` method and provide it with `{ provide: ErrorHandler, useClass: MyErrorHandler }` in `ApplicationConfig` (or the root NgModule). It is for **reporting** unexpected errors — logging, monitoring — not for recovering: errors you can handle belong in a `try`/`catch` or RxJS `catchError` where they happen.

code

ts · 16 lines
ts
import { ApplicationConfig, ErrorHandler, Injectable, provideBrowserGlobalErrorListeners } from '@angular/core';

@Injectable()
export class ReportingErrorHandler implements ErrorHandler {
  handleError(error: unknown): void {
    console.error('Unhandled application error', error);
    // forward to your monitoring endpoint here, without ever throwing
  }
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    { provide: ErrorHandler, useClass: ReportingErrorHandler },
  ],
};

go deeper

for a junior

Recall that the default only logs to the console and that you replace it with a class provided under the ErrorHandler token.

for a middle

Explain which framework-driven code paths feed the handler and why it is for reporting unexpected errors, not recovery.

for a senior

Harden the handler: normalise non-Error values, never throw from it, keep its dependencies minimal and pair it with global listeners.

for a principal

Define the error-reporting contract for the whole app: what is handled locally, what is reported globally, and who is alerted.

## What `ErrorHandler` is `ErrorHandler` is a class exported from `@angular/core`. Angular looks up one instance from the application's root injector and calls its `handleError(error)` method whenever the framework itself catches an error that your code did not handle. That happens mainly in code Angular calls on your behalf, where you have nowhere to put a `try` block: - component and directive constructors and lifecycle hooks such as `ngOnInit`; - template expressions evaluated during change detection; - event listeners bound in templates, such as `(click)="save()"`; - a few APIs with an explicit contract to consume async results, such as the `async` pipe; - application bootstrap, including failing startup initializers. ## What the default does The built-in implementation is tiny: `handleError` calls `console.error('ERROR', error)`. It does not rethrow, does not stop the app and does not send anything anywhere. In a browser, a user who hits an error sees a component that stopped updating or a button that did nothing, while the only evidence sits in their own console. ## Replacing it 1. Write a class that `implements ErrorHandler` and defines `handleError(error: unknown): void`. 2. Mark it `@Injectable()` if it needs dependencies, or use `inject()` in field initializers. 3. Provide it at application level with `{ provide: ErrorHandler, useClass: MyErrorHandler }` in `ApplicationConfig.providers` — or in the root NgModule's `providers` in a module-based app. Angular's general error path looks the handler up from the application's root environment injector, so providing a different `ErrorHandler` in a component's or lazy route's `providers` does not give that part of the app its own handler for hook, binding or listener errors. | Aspect | Default `ErrorHandler` | Custom handler | |---|---|---| | Output | `console.error('ERROR', error)` | Whatever you implement: console, monitoring, a UI notice | | Provided by | The browser platform providers | You, with `useClass` in the app providers | | Stops the app | No | No, unless your code does | | Typical addition | — | Context (current URL, build version), deduplication | ## What to put in it - **Keep the console output** in development, or you lose the stack trace you rely on while debugging. - **Normalise the argument.** The parameter is not always an `Error`: it can be a string, an `HttpErrorResponse` or any value someone threw. - **Never throw from it.** A handler that throws loses the original report; wrap reporting code in `try`/`catch`. - **Keep it independent.** Avoid dependencies that might be the thing that failed; Angular resolves your handler lazily for this reason, but the handler's own dependencies still need to be healthy. ## What it is not for The Angular guidance is explicit: errors reported to `ErrorHandler` are **unexpected** errors, possibly leaving the application in a corrupted state. Handling an expected failure — a 404 from an API, a validation error — belongs at the call site with `try`/`catch` or `catchError`, where the code has the context to show a message or retry. The global handler is the last line: record it, alert on it, perhaps show a generic notice. ## Module-based applications In an app bootstrapped with `bootstrapModule`, the same provider goes into the root NgModule's `providers`. The default handler there comes from the browser platform's providers, which `BrowserModule` brings in; the replacement simply overrides that token in the root injector. Because there is one root handler, feature modules should not each provide their own: the last registration in the root injector wins, and lazily loaded modules have their own injectors that framework-level reporting does not consult. ## A short checklist for a custom handler 1. Implements `ErrorHandler`, provided with `useClass` at the root. 2. Logs to the console, at least in development. 3. Normalises the value before using it. 4. Cannot throw. 5. Is paired with `provideBrowserGlobalErrorListeners()` so window-level errors reach it. ## Related pieces - `provideBrowserGlobalErrorListeners()` (v20+, included by the CLI in new apps) forwards the window's `error` and `unhandledrejection` events to the same handler, which matters most in zoneless applications. - In unit tests, `TestBed` rethrows errors that reach the handler by default, so a failing hook fails the test rather than only logging. - An optional `onViewError` method, used by the developer-preview `@boundary` block, receives errors that a template boundary caught.

  • Does the default ErrorHandler stop the application after an error?
    No. It only logs with `console.error` and returns, so the app keeps running. The part of the view that threw may be left half-updated, which is why the guidance treats these errors as unexpected and possibly state-corrupting, and why you report them rather than silently continue.
  • Why does TestBed seem to fail tests on errors that only get logged in the browser?
    `TestBed` rethrows errors that reach the application error handler by default, so a throwing lifecycle hook fails the test instead of printing to the console. A test that deliberately checks resilience can turn that off with `rethrowApplicationErrors: false` in `configureTestingModule`.

The default ErrorHandler is a smoke alarm with no link to the fire station: it makes noise in the room where it went off, and nobody else ever finds out. A custom handler wires it to the station.

saying these in an interview costs you the question

  • The default ErrorHandler reloads or stops the application
  • ErrorHandler catches every exception thrown anywhere in the app
  • A component-level ErrorHandler provider receives that component's hook and listener errors
  • handleError always receives an Error instance
  • The global handler is the right place to handle expected API failures
open as a page

In Angular, which errors reach the ErrorHandler automatically, and which ones never get there?

level: middleimportance: must knowfreq 50%

basics

~20 s

Errors thrown in code Angular runs for you — constructors, lifecycle hooks, template bindings, template event listeners, async pipe sources, bootstrap — reach ErrorHandler. Errors you catch, errors exposed as state like resource().error, and free async errors without global listeners do not.

open as a page

You must send every uncaught Angular error to a monitoring endpoint; how would you write the custom ErrorHandler, and what pitfalls would you avoid?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Implement ErrorHandler, normalise the value into name, message and stack, add context such as the current URL, and send it with navigator.sendBeacon or fetch. Wrap everything in try/catch, deduplicate and cap reports, and keep console output.

open as a page

After an Angular app goes zoneless, errors from setTimeout callbacks and un-awaited promises stop reaching the ErrorHandler; why, and what fixes it?

level: seniorimportance: should knowfreq 35%

basics

~10 s

With zone.js, errors in async tasks inside the Angular zone were forwarded to ErrorHandler through the zone. Zoneless removes that path. provideBrowserGlobalErrorListeners() restores it by forwarding window error and unhandledrejection events to ErrorHandler.

open as a page

In Angular templates, what does the developer-preview @boundary block with @error catch, and what does it not catch?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

@boundary catches errors thrown while the components inside it are created or checked, and renders its @error block instead, exposing $error and $reset. Errors from event listeners, independent async code and projected content are not caught by it.

open as a page