angular.courses

Standalone components vs NgModules

When to use standalone components and when NgModules still make sense; comparison and coexistence

Overview

Angular supports both standalone components and NgModules. New applications and components should prefer standalone; NgModules remain for lazy-loaded feature modules, shared modules in legacy code, and some library packaging. This practice compares the two and explains when to use each and how they coexist.

The Evolution

v2NgModule-only

All components, directives, and pipes had to be declared in an NgModule.

v14Standalone (preview)

Standalone components and directives introduced as opt-in.

v15Standalone vs NgModules

Standalone recommended for new code; NgModules still used where needed.

Comparison

Bootstrapping

NgModule bootstrapBefore v17
main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
Standalone bootstrapv14+
main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent);

Declaring dependencies

NgModule: declarations & importsBefore v17
@NgModule({
declarations: [MyComponent, MyDirective, MyPipe],
imports: [CommonModule, OtherFeatureModule],
exports: [MyComponent],
})
export class MyModule {}
// Component does not declare its own dependencies
Standalone: imports on componentv14+
@Component({
selector: 'app-my',
standalone: true,
imports: [CommonModule, OtherStandaloneComponent],
template: `...`,
})
export class MyComponent {}
// Component declares exactly what it uses

Lazy loading

Lazy load NgModuleBefore v17
const routes: Routes = [
{
path: 'admin',
loadChildren: () =>
import('./admin/admin.module').then(m => m.AdminModule),
},
];
// AdminModule declares and exports its components
Lazy load standalone routev14+
const routes: Routes = [
{
path: 'admin',
loadChildren: () =>
import('./admin/admin.routes').then(m => m.adminRoutes),
},
];
// admin.routes.ts exports Routes with standalone components

Shared pieces: SharedModule vs standalone imports

SharedModuleBefore v17
@NgModule({
declarations: [SharedPipe, SharedDirective],
exports: [SharedPipe, SharedDirective],
})
export class SharedModule {}
// Consumer module
@NgModule({
imports: [SharedModule],
declarations: [FeatureComponent],
})
export class FeatureModule {}
Standalone: import what you needv14+
// Shared pipe/directive are standalone
@Component({
standalone: true,
imports: [SharedPipe, SharedDirective],
template: `...`,
})
export class FeatureComponent {}

Key Differences

Unit of reuse:NgModule (declarations + exports)Component/directive/pipe (imports array)
Bootstrap:bootstrapModule(AppModule)bootstrapApplication(AppComponent)
Lazy loading:loadChildren → NgModuleloadChildren → Routes or loadComponent
Visibility:Module exports; declaration scopeExplicit imports per component

Benefits

Simplicity

No separate NgModule file for most features

Tree-shaking

Only imported code is bundled

Clarity

Dependencies visible on the component

Coexistence

Standalone and NgModules work together

Migration

Gradual move from modules to standalone

When to use standalone

  • New apps and components: Use standalone: true and bootstrapApplication.
  • New lazy routes: Use loadChildren returning routes or loadComponent for a single component.
  • Libraries: Publish standalone APIs; consumers import only what they use.

When NgModules still make sense

  • Existing code: Keep NgModules during gradual migration.
  • Lazy-loaded feature modules: You can keep a feature as an NgModule and lazy-load it until you refactor to standalone routes.
  • Some third-party libraries: May still expose an NgModule; import it in your standalone component’s imports or in importProvidersFrom() when bootstrapping.

Mixing both

You can use importProvidersFrom(SomeModule) in bootstrapApplication(AppComponent, { providers: [...], }) to pull providers from an NgModule into a standalone app. Standalone components can also list NgModules in their imports array.

Default in Angular 19+

New components are generated with standalone: true by default. Use NgModules only where you have a concrete reason (e.g. legacy module, library contract).

Coexistence

| Scenario | Approach | |----------|----------| | New feature | Standalone component + imports | | Legacy feature module | Keep NgModule; lazy-load it; migrate when convenient | | Shared directives/pipes | Prefer standalone; add to each component’s imports | | App bootstrap | bootstrapApplication with root standalone component | | Providers from module | importProvidersFrom(MyModule) in bootstrap or route config |