skip to content

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%

answer

  1. platform method versus plain function
  2. bootstrap array versus first argument
  3. NgModuleRef versus ApplicationRef
  4. EnvironmentProviders fit module providers

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.

solid answer

~30 s

`platformBrowser().bootstrapModule(AppModule)` reads the root module: it imports `BrowserModule`, names the root in `bootstrap`, and supplies app-wide providers; it resolves to an `NgModuleRef`. `bootstrapApplication(App, appConfig)` takes a standalone root and an `ApplicationConfig` of providers, registers the browser infrastructure itself, and resolves to an `ApplicationRef`. Underneath, both build a root environment injector and an `ApplicationRef`. Because `provideRouter`, `provideHttpClient` and similar functions return `EnvironmentProviders`, which NgModule `providers` accept, a module-based app can adopt them first, for example replacing the deprecated `HttpClientModule`, and switch the bootstrap call last.

code

ts · 18 lines
ts
import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { App } from './app';
import { routes } from './app.routes';

@NgModule({
  declarations: [App], // standalone: false
  imports: [BrowserModule],
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes),
    provideHttpClient(withInterceptorsFromDi()),
  ],
  bootstrap: [App],
})
export class AppModule {}

go deeper

for a junior

Know both main.ts shapes, that new projects use bootstrapApplication, and that module-based apps name the root in the module's bootstrap array.

for a middle

Compare return types, root selection and where providers live, and explain why EnvironmentProviders from provideX functions work in NgModule providers.

for a senior

Plan the root-level migration: adopt provideX functions inside AppModule, avoid double router registration, and switch the bootstrap call last.

for a principal

Judge when switching the bootstrap is worth doing at all for a stable module-based app, versus adopting only the function-based providers and standalone features.

## Two entry points Angular has two ways to start an application in the browser: ```ts // Module-based import { platformBrowser } from '@angular/platform-browser'; import { AppModule } from './app/app.module'; platformBrowser().bootstrapModule(AppModule).catch((err) => console.error(err)); ``` ```ts // Standalone import { bootstrapApplication } from '@angular/platform-browser'; import { App } from './app/app'; import { appConfig } from './app/app.config'; bootstrapApplication(App, appConfig).catch((err) => console.error(err)); ``` The Angular documentation recommends `bootstrapApplication` for all new code, and `ng new` generates a standalone app by default; `ng new --standalone=false` still produces the module-based shape, with an `AppModule` that imports `BrowserModule` and lists `App` in `bootstrap`. ## Side by side | | `bootstrapModule(AppModule)` | `bootstrapApplication(App, appConfig)` | |---|---|---| | Called on | a platform: `platformBrowser()` | a plain function from `@angular/platform-browser` | | Root component chosen by | the module's `bootstrap` array | the first argument, which must be standalone | | Browser infrastructure | `BrowserModule` in the root module's `imports` | registered by the call itself | | App-wide providers | the root module's `providers` and imported modules | `appConfig.providers` | | Returns | `Promise<NgModuleRef<AppModule>>` | `Promise<ApplicationRef>` | | Error for a bad root | `NG0403` if there is no `bootstrap` entry and no `ngDoBootstrap` | a development-mode error for a non-standalone root | Both paths create the same kind of application underneath: a root environment injector, an `ApplicationRef`, and the root component attached to `index.html`. The differences are in how you *describe* the application, not in what runs. ## Can a module-based app use `provideRouter` or `provideHttpClient`? Yes. Functions such as `provideRouter(routes)`, `provideHttpClient(...)` and `provideClientHydration()` return `EnvironmentProviders`, and an NgModule's `providers` array accepts them. The Angular documentation shows `provideHttpClient(...)` in an `AppModule`'s providers, and the CLI's module template puts `provideBrowserGlobalErrorListeners()` there. This matters for migration. A module-based app can adopt the function-based APIs **before** switching its bootstrap: - Replace the deprecated `HttpClientModule` with `provideHttpClient(withInterceptorsFromDi())` in `AppModule.providers`. - Keep `RouterModule.forRoot(routes)` or move to `provideRouter(routes)` — but not both, since each registers a `Router`. - Add `provideClientHydration()` to the root module's providers when enabling SSR hydration. By the time the bootstrap switch happens, the root module's `providers` array is already the future `appConfig.providers`, and the switch is mostly a copy. ## Defaults that apply to both Recent defaults do not depend on the bootstrap style: - Zoneless change detection is the default since v21 for module-based and standalone apps alike; `provideZoneChangeDetection()` in the providers opts back in. - Standalone is the default for components since v19, so a module-based app's declared components must say `standalone: false`. ## What `bootstrapModule` still offers The second argument of `bootstrapModule` accepts compiler and bootstrap options. The zone-related bootstrap options (`ngZone`, `ngZoneEventCoalescing`) are deprecated in favour of providing `provideZoneChangeDetection(...)` in the module's providers, which is the same provider a standalone app would use. In practice, nothing about `bootstrapModule` blocks moving to function-based providers first. ## What changes in the root component The root component is where the two styles differ most visibly: - In a module-based app, `App` is declared in `AppModule` with `standalone: false`, and its template can use everything in `AppModule`'s compilation scope — its own declarations and the exports of `BrowserModule`, `AppRoutingModule` and any other imports. - In a standalone app, `App` is standalone and lists its own `imports`, such as `RouterOutlet` for `<router-outlet>` and any shared components it renders directly. So the bootstrap switch is never just a change to `main.ts`. The root component has to become standalone and gain the imports its template relies on, and anything `AppModule` did in its constructor must find a new home, typically an initializer in the new config. ## A worked comparison of the two `main.ts` files Putting the pieces together, the migration of the entry point is: 1. `AppModule.providers` becomes `appConfig.providers`, unchanged. 2. `BrowserModule` disappears; `bootstrapApplication` registers the same browser providers itself. 3. `AppRoutingModule` with `RouterModule.forRoot(routes)` becomes `provideRouter(routes)` in the config. 4. The `bootstrap: [App]` entry becomes the first argument of `bootstrapApplication`. 5. `AppModule` is deleted. Each line of the old module maps to exactly one place in the new setup, which is why the bootstrap switch is quick once the providers were already function-based. ## Choosing between them - **New app:** `bootstrapApplication`. - **Existing module-based app:** keep `bootstrapModule` while migrating, adopt `provideX()` functions in `AppModule.providers`, and switch the bootstrap call last, when `AppModule` has become little more than a list of providers.

  • Why should AppModule not use RouterModule.forRoot(routes) and provideRouter(routes) together?
    Both register the router's root services, so the app would configure the `Router` twice from two sources. Pick one: keep `RouterModule.forRoot` until you are ready, then replace it with `provideRouter` and move any router options to `withX()` features. Feature modules can keep `RouterModule.forChild` meanwhile.
  • What does the switch from bootstrapModule to bootstrapApplication involve once AppModule only holds providers?
    Make the root component standalone and give it its own `imports`, move `AppModule.providers` into an `appConfig` object, drop `BrowserModule` because `bootstrapApplication` registers it, call `bootstrapApplication(App, appConfig)` in `main.ts`, and delete `AppModule`.

saying these in an interview costs you the question

  • provideRouter and provideHttpClient only work with bootstrapApplication
  • bootstrapApplication and bootstrapModule create different kinds of applications
  • A standalone component can be listed in an NgModule's bootstrap array
  • Module-based apps keep zone.js by default in v22
  • You must switch bootstrap before adopting any provideX function