skip to content

Standalone Application Setup

Starting an app with bootstrapApplication, an ApplicationConfig of provideX functions, and importProvidersFrom for module-only libraries. Interviewers check you can wire an app without an AppModule.

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

explore

questions

4

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

level: juniorimportance: must knowfreq 72%

answer

  1. one call in main.ts
  2. root must be standalone
  3. a single providers array
  4. returns Promise<ApplicationRef>

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>.

solid answer

~30 s

`main.ts` calls `bootstrapApplication(App, appConfig)` from `@angular/platform-browser`. The root component must be standalone, which is the default since v19, so there is no `AppModule`. `ApplicationConfig` is an interface with one field, `providers`, holding `Provider` and `EnvironmentProviders` entries: plain recipes, feature functions such as `provideRouter(routes)` or `provideHttpClient()`, and `importProvidersFrom(...)` for NgModule-only libraries. Those providers land in the root environment injector, visible to the whole app. The call registers the browser infrastructure itself, so `BrowserModule` is not imported, and it returns a `Promise<ApplicationRef>` that rejects if startup throws, which is why the CLI appends `.catch()`.

code

ts · 10 lines
ts
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes),
  ],
};

go deeper

for a junior

Recall the two files: main.ts calls bootstrapApplication with the root component and appConfig, and app.config.ts exports a providers array. Know that no AppModule is involved.

for a middle

Explain what the providers array accepts, that feature functions return EnvironmentProviders, why BrowserModule is gone, and that the call returns a promise of ApplicationRef.

for a senior

Show you can reason about startup failures: which errors reject the bootstrap promise, and why app-wide providers belong in the config rather than on a component.

for a principal

Frame standalone bootstrap as the app's single composition root, and argue for keeping it a short, reviewable list of provideX calls rather than a dumping ground.

## What bootstrapping means in a standalone Angular app **Bootstrapping** is the step that turns a folder of TypeScript classes into a running application: Angular creates a platform, builds the application's root **environment injector**, instantiates the root component, attaches it to the matching element in `index.html`, and starts change detection. In a standalone app (the default for components, directives and pipes since v19) the whole step is one call in `main.ts`: ```ts import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; import { App } from './app/app'; bootstrapApplication(App, appConfig).catch((err) => console.error(err)); ``` There is no `AppModule`, no `declarations` array and no `bootstrap: [...]` field. The Angular CLI generates exactly this shape for a new project. ## The three arguments of `bootstrapApplication` `bootstrapApplication` is exported from `@angular/platform-browser` and takes up to three arguments: 1. **`rootComponent`** — the class to render first. It **must** be a standalone component. Because standalone is the default, a plain `@Component({...})` class with no `standalone` flag qualifies; a component that declares `standalone: false` and lives in an NgModule's `declarations` does not. 2. **`options`** — an optional `ApplicationConfig`, conventionally exported as `appConfig` from `app.config.ts`. 3. **`context`** — an optional `BootstrapContext`. The browser never passes it; server rendering does, so that each request gets its own platform (the server side is a separate subject). The call is `async` and returns a **`Promise<ApplicationRef>`**. The promise resolves once the root component is created and attached; it rejects if anything during startup throws — a provider factory, an initializer, or a root selector that matches no element. That is why the generated `main.ts` ends with `.catch(...)`. ## What goes into `ApplicationConfig` `ApplicationConfig` is a small interface from `@angular/core`. Its only field is: ```ts interface ApplicationConfig { providers: Array<Provider | EnvironmentProviders>; } ``` Everything an app needs application-wide is expressed as a provider in that one array: - **Plain providers** — a class, or a recipe such as `{ provide: API_URL, useValue: '/api' }`. - **`provideX()` feature functions** — `provideRouter(routes)`, `provideHttpClient(...)`, `provideClientHydration()`, `provideBrowserGlobalErrorListeners()`. They return `EnvironmentProviders`, a type that is only accepted by environment injectors. - **`importProvidersFrom(SomeModule)`** — the bridge for a library that still ships only an NgModule. The CLI's generated `app.config.ts` for a new v22 project with routing contains `provideBrowserGlobalErrorListeners()` and `provideRouter(routes)`. It no longer contains a zone provider, because zoneless change detection is the default since v21. Providers listed here are registered in the **root environment injector**, so they are visible to the root component and everything below it — every component, directive, pipe, service and lazily-loaded route. ## What `bootstrapApplication` adds on its own The call already registers the browser infrastructure that `BrowserModule` used to bring: the DOM renderer, the event manager and its key-event plugin, a default `ErrorHandler`, and the `DOCUMENT` and platform-id tokens. You do not import `BrowserModule` into a standalone app. Template building blocks such as `@if` and `@for` are built into the template syntax, and anything else a template uses is listed in that component's own `imports` array rather than in the app config. ## Why `appConfig` lives in its own file Nothing forces the config into a separate module — `bootstrapApplication(App, { providers: [...] })` works inline. The CLI splits it into `app.config.ts` for practical reasons: - **Reuse.** A server-rendered app bootstraps twice, once in the browser and once on the server. The server build imports the same `appConfig` and merges its own providers on top with `mergeApplicationConfig`, so shared providers are written once. - **Tests and tools.** A test or a script can import the config without executing the bootstrap side effect that `main.ts` performs. - **Review.** The file is the application's single **composition root**: one short list that says which framework features are switched on. When a reviewer wants to know whether the app uses hydration, a custom error listener or a module-only library, this is the one place to look. Keeping `main.ts` to a single call also means the entry point never grows logic of its own; configuration changes are made in the config, not in the startup script. ## Module bootstrap compared | Concern | NgModule app | Standalone app | |---|---|---| | Entry call | `platformBrowser().bootstrapModule(AppModule)` | `bootstrapApplication(App, appConfig)` | | Where app-wide providers live | `@NgModule({ providers })`, `forRoot()` imports | `appConfig.providers` | | Where template dependencies live | `declarations` / `imports` of the module | each component's own `imports` | | Root component chosen by | `bootstrap: [App]` | the first argument | ## Common mistakes - Putting `provideRouter` or `provideHttpClient` in a **component's** `providers` array — they return `EnvironmentProviders`, which a component injector rejects. - Believing `ApplicationConfig` has `imports` or `declarations` fields — it has only `providers`. - Forgetting that the returned value is a promise and swallowing bootstrap failures. - Passing a `standalone: false` component as the root.

  • Why does the CLI-generated main.ts end with .catch((err) => console.error(err))?
    `bootstrapApplication` is async and returns a `Promise<ApplicationRef>`. If startup throws, for example a provider factory fails, an initializer rejects, or the root selector matches no element in `index.html`, the promise rejects. Without a `.catch`, that surfaces as an unhandled promise rejection instead of a logged bootstrap error.
  • Do you still import BrowserModule or CommonModule anywhere in a standalone app?
    No. `bootstrapApplication` registers the browser providers `BrowserModule` used to supply, such as the DOM renderer, event manager and default `ErrorHandler`. Built-in control flow needs no import, and a component that uses a pipe or directive lists that class, not `CommonModule`, in its own `imports`.

saying these in an interview costs you the question

  • bootstrapApplication still needs an AppModule with a bootstrap array
  • ApplicationConfig has imports and declarations fields like an NgModule
  • You must add BrowserModule to the providers for the app to render
  • Any component, including one with standalone: false, can be the root
  • bootstrapApplication returns the root ComponentRef synchronously
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

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