AppModule in the standalone era: what moved to components, bootstrapApplication, and route providers?

LowIntermediateAngular
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

With standalone, AppModule is no longer the central “assembly point”. Template scope moves from NgModule declarations/imports to each standalone component’s imports, and app-wide configuration moves to bootstrapApplication(...) (or an ApplicationConfig) and route-level providers. AppModule often disappears in greenfield apps, but NgModules still exist for legacy code and module-shaped libraries. The trade-off is simpler bootstrapping versus managing provider scope per component; test DI boundaries when migrating.

Answer

Core idea

Standalone changes Angular’s composition model:

Before: AppModule declared what exists and what templates can use.
Now: standalone components declare their own template dependencies (imports), and app-wide wiring happens in bootstrapApplication (or ApplicationConfig) + route providers.

AppModule responsibility (NgModule-era)

Standalone replacement (where it moved)

Concrete example

Bootstrapping

main.ts uses bootstrapApplication

bootstrapApplication(AppComponent, appConfig)

Template dependency scope (CommonModule/Router/Material/etc.)

Standalone component imports (or feature route component imports)

@Component({ standalone:true, imports:[NgIf, NgFor, RouterOutlet] })

Global providers via module imports

Provider functions + environment providers

provideRouter, provideHttpClient, provideAnimations

Feature modules for routing + lazy loading

Standalone routes + loadComponent (and route-level providers)

{ path:'admin', loadComponent: () => import(...), providers:[...] }

Legacy declarables packaging

Still NgModules (compat layer)

Standalone components import the legacy NgModule that exports them

What “moved out of AppModule” and where it lives now
TYPESCRIPT
// NgModule-era (classic)
// main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule).catch(console.error);

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule, RouterModule.forRoot([])],
  bootstrap: [AppComponent]
})
export class AppModule {}
                  
TYPESCRIPT
// Standalone-era (modern)
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(
      withInterceptors([
        // (req, next) => next(req)
      ])
    ),
    provideAnimations()
  ]
};

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { appConfig } from './app.config';

bootstrapApplication(AppComponent, appConfig).catch(console.error);
                  
TYPESCRIPT
// app.component.ts (standalone root)
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `<router-outlet></router-outlet>`
})
export class AppComponent {}
                  

Interview hotspot: “Where do global things go now?”

Standalone answer

RouterModule.forRoot(...)

provideRouter(routes) in bootstrapApplication / ApplicationConfig

HttpClientModule

provideHttpClient(...) (interceptors via withInterceptors/withInterceptorsFromDi)

BrowserAnimationsModule

provideAnimations() (or provideNoopAnimations())

Feature-scoped providers

Route-level providers: [...] (scoped to that route subtree)

Module-only libraries (forRoot/NgModules)

Use importProvidersFrom(SomeLibModule.forRoot(...)) (or keep AppModule if simpler)

This is the practical “AppModule replacement” map interviewers look for
TYPESCRIPT
// Route-level providers (feature scoping without a feature module)
import { Routes } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';

export const routes: Routes = [
  {
    path: 'admin',
    loadComponent: () => import('./admin/admin.page').then(m => m.AdminPage),
    providers: [
      // providers here are scoped to the admin route subtree
      provideHttpClient()
    ]
  }
];
                  

Common standalone migration pitfall

What breaks

Fix

Forgetting template imports (NgIf/NgFor/AsyncPipe/etc.)

Template compile errors: “Can’t bind to ...” / unknown directive/pipe

Import the standalone directive/pipe (e.g. NgIf, NgFor, AsyncPipe) or import a legacy NgModule that exports them

Mixing module config patterns blindly

Duplicate providers / unexpected multiple instances

Prefer provider functions at bootstrap/route; use importProvidersFrom only when the library forces NgModule shape

Assuming NgModules are “gone”

Legacy libs/declarables can’t be imported directly

Keep NgModules as a compatibility/container layer where needed

What experienced Angular devs call out quickly

Do you still need AppModule?

Answer

Greenfield standalone app

Usually no — AppModule can disappear entirely.

Incremental migration from a large NgModule app

Often yes (temporarily) — convert routes/components gradually while keeping AppModule as glue.

Library ecosystem is NgModule-heavy (forRoot/exported modules)

You might still avoid AppModule, but you’ll rely on importProvidersFrom(...) and some NgModules as adapters.

The senior answer is “AppModule becomes optional; NgModules become mostly compatibility/packaging.”

Practical scenario
Migrating a legacy Angular app to standalone components, you move imports and providers from AppModule into each feature component.

Common pitfalls

      • Forgetting to add required imports to a standalone component.
      • Provider scope changes causing unexpected singleton behavior.
      • Mixing module-based libraries without clear boundaries.
Trade-off or test tip
Standalone simplifies bootstrapping but shifts responsibility to each component. Test DI scopes and route-level providers after migration.

Summary

Standalone doesn’t delete NgModules, but it removes the need for an AppModule as the app’s “wiring center”. Template scope moves to standalone component imports. App-wide wiring moves to bootstrapApplication (or ApplicationConfig) and can be further scoped via route-level providers. AppModule becomes optional: common to remove in new apps, common to keep temporarily during migrations.

Similar questions
Guides
13 / 37