In Angular, how does bootstrapModule(AppModule) differ from bootstrapApplication(App, appConfig), and can a module-based app use provideRouter or provideHttpClient?
answer
- platform method versus plain function
- bootstrap array versus first argument
- NgModuleRef versus ApplicationRef
- EnvironmentProviders fit module providers
basics
~20 sbootstrapModule 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 linesimport { 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
Know both main.ts shapes, that new projects use bootstrapApplication, and that module-based apps name the root in the module's bootstrap array.
Compare return types, root selection and where providers live, and explain why EnvironmentProviders from provideX functions work in NgModule providers.
Plan the root-level migration: adopt provideX functions inside AppModule, avoid double router registration, and switch the bootstrap call last.
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