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

HighIntermediateAngular
Quick Answer

This question is about standalone migration execution: what moved from AppModule into bootstrapApplication or ApplicationConfig, component imports, and route providers, plus the provider-scope and route-wiring bugs teams hit during real migrations.

Answer

Migration map

Standalone Angular does not mean “move AppModule into AppComponent.” The production change is more specific: bootstrapping and global providers move to bootstrapApplication / ApplicationConfig, template dependencies move into each standalone component’s imports, and feature-scoped services move to route providers. The main pitfall is accidentally changing DI scope while you migrate.

Question intent

What to emphasize

What to de-emphasize

This page: AppModule migration map

Where each AppModule responsibility moves: bootstrap, global providers, component imports, route-level providers, and DI scope checks.

Long debates about whether NgModules should exist conceptually.

Related page: NgModules vs standalone boundaries

Compatibility cases: legacy declarables, forRoot/forChild module-shaped libs, packaging/export boundaries.

Step-by-step migration runbook from AppModule to standalone wiring.

Use this question to prove migration execution depth, not just theory.

Scope guard

If the interviewer asks “Do NgModules still have value?”, that is the companion question: <a href="/angular/trivia/angular-ngmodules-vs-standalone">What problems do NgModules solve that standalone components don’t?</a>.

This page answers a different prompt: “I have an AppModule app; what exactly do I move, to where, and how do I avoid provider-scope regressions?”

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 does remove AppModule as the required wiring center. The senior answer here is operational: map each AppModule responsibility to its standalone destination, migrate providers deliberately, and verify DI scope at route/feature boundaries. Keep this answer migration-focused; keep compatibility/packaging trade-offs in the separate NgModules-vs-standalone discussion.

Similar questions
Guides
Preparing for interviews?

Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.