skip to content

Bootstrapping & Modules

How an Angular app is assembled and started: bootstrapApplication with provider functions, NgModules, startup initializers and the ErrorHandler. Interviews probe whether you know the standalone era.

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

explore

questions

22

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, what does provideAppInitializer do, and why did it replace the APP_INITIALIZER token?

level: juniorimportance: must knowfreq 52%

basics

~10 s

provideAppInitializer registers a function Angular runs during bootstrap, before the root component is created; if it returns a Promise or Observable, startup waits for it. It replaced the APP_INITIALIZER multi-provider token, deprecated since v19.

open as a page

In Angular, what do the declarations, imports, exports, providers and bootstrap fields of an @NgModule each do?

level: juniorimportance: must knowfreq 70%

basics

~10 s

declarations lists the non-standalone components, directives and pipes a module owns; imports brings in what its templates use; exports shares declarables with importers; providers registers services; bootstrap names the root component bootstrapModule renders.

open as a page

In Angular, how does bootstrapApplication start a standalone app, and what belongs in the ApplicationConfig passed to it?

level: juniorimportance: must knowfreq 72%

basics

~10 s

bootstrapApplication(App, appConfig) in main.ts renders a standalone root component and builds the root injector from ApplicationConfig, whose only field is a providers array of plain providers and provideX() functions. It returns a Promise<ApplicationRef>.

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

In an Angular app, how would you load settings from /config.json at runtime so every service sees them before the app starts?

level: middleimportance: must knowfreq 60%

basics

~20 s

Put a config service in root, give it a load() method that fetches /config.json, and register provideAppInitializer(() => inject(ConfigService).load()). Angular waits for that Promise, so the values are set before the root component and its services run.

open as a page

In Angular, how do you use an NgModule's components inside a standalone component, and a standalone component inside an NgModule?

level: middleimportance: must knowfreq 64%

basics

~20 s

Both directions use imports: a standalone component lists the NgModule in its own imports and gets that module's exports; an NgModule lists the standalone class in its imports, and may re-export it, but never declares it (NG6008).

open as a page

In a module-based Angular app, why does a template fail with 'app-user-card is not a known element', and how does NgModule compilation scope explain the fix?

level: middleimportance: must knowfreq 68%

basics

~20 s

A declared component's template can only use its own module's declarations plus what imported modules export. The error means UserCard is not in that scope: export it from its module and import that module into the one declaring the failing template.

open as a page

In Angular, when several provideAppInitializer functions return Promises, do they run one after another or in parallel, and when does rendering start?

level: middleimportance: should knowfreq 38%

basics

~10 s

Angular calls each initializer synchronously in provider order, then waits for all returned Promises and Observables together, so their async work overlaps. The root component is created only after every one has settled successfully.

open as a page

In Angular, how does bootstrapModule(AppModule) differ from bootstrapApplication(App, appConfig), and can a module-based app use provideRouter or provideHttpClient?

level: middleimportance: should knowfreq 50%

basics

~20 s

bootstrapModule starts an app from a root NgModule whose bootstrap array names the root component; bootstrapApplication starts from a standalone root plus an ApplicationConfig. Module-based apps can use provideRouter and provideHttpClient in the root module's providers.

open as a page

In Angular, why do modules such as RouterModule expose forRoot() and forChild(), and what goes wrong if a lazy-loaded module imports forRoot()?

level: middleimportance: should knowfreq 57%

basics

~20 s

forRoot() returns the module plus its singleton services and global config, imported once at the root; forChild() returns it with only feature config. A lazy module importing forRoot() re-registers services in its child injector, duplicating singletons.

open as a page

For a new Angular 22 app with routing, an HttpClient auth interceptor and SSR hydration, what goes into app.config.ts, and why?

level: middleimportance: should knowfreq 58%

basics

~10 s

app.config.ts lists provideBrowserGlobalErrorListeners(), provideRouter(routes), provideHttpClient(withInterceptors([auth])) and provideClientHydration(withEventReplay()); zoneless, fetch and incremental hydration are v21-v22 defaults, so no extra lines are needed for them.

open as a page

In an Angular standalone app, when do you still need importProvidersFrom, and what does it deliberately not give you?

level: middleimportance: should knowfreq 52%

basics

~20 s

importProvidersFrom bridges NgModule-only libraries into a standalone app: it collects a module's providers, transitively, as EnvironmentProviders. It gives no components, directives or pipes, and it only works in app or route providers, never in a component's.

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

An Angular app loads its config in provideAppInitializer and sometimes shows only a blank page in production; how do you diagnose and harden startup?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A blank page means initialization either rejected or never settled. Check the console and ErrorHandler for a rejection, look for a hanging request or an Observable that never completes, then add a timeout, a deliberate fallback and a visible error in main.ts.

open as a page

A module-based Angular app wants its first standalone feature, a lazily loaded reports area; how do you add it without converting the rest of the app?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Build the reports pages as standalone components that import existing NgModules for old pieces, load them through a lazy Routes array from the existing RouterModule.forRoot config, and provide feature services on the route. Keep shared modules free of providers to avoid duplicates.

open as a page

A legacy Angular SharedModule imports and re-exports forty components, three form and common modules, and provides services; what problems does it cause and how would you break it up?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Its providers are re-registered in every lazy module's injector, duplicating singletons, and its huge re-export scope hides what each template uses. Move services to providedIn: 'root' first, then split components into SCAMs or standalone components imported directly.

open as a page

How would you sequence an incremental standalone migration of a large module-based Angular app so that every intermediate step ships safely?

level: principalimportance: should knowfreq 32%

basics

~20 s

Fix provider hygiene first, write new features standalone, convert leaf declarables and import them into their old modules, delete emptied modules, and switch bootstrapModule to bootstrapApplication last. Interop keeps every step shippable, and stopping early is a legitimate choice.

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

In Angular, how does provideEnvironmentInitializer differ from provideAppInitializer, and when would you use it in a lazy route?

level: middleimportance: nice to knowfreq 25%

basics

~10 s

provideEnvironmentInitializer runs a synchronous function whenever the environment injector holding it is created, and never awaits it. provideAppInitializer runs once at bootstrap and holds rendering until its Promise or Observable settles.

open as a page

In an Angular SSR app, a provider override added to app.config.server.ts never takes effect; how does mergeApplicationConfig combine configs, and what would you check?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

mergeApplicationConfig concatenates providers arrays left to right without de-duplicating; the injector then keeps the last regular provider per token and accumulates multi providers. Check argument order (shared first, server second), multi tokens, and closer injectors.

open as a page