skip to content

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