Standalone Components vs NgModules: An Angular Architecture Decision Guide
Choose standalone components for new Angular apps; keep NgModules only for legacy code or library compatibility.
23 Dec 2025, 03:02 UTC

The decision and the constraints
When starting a new Angular application or adding a feature module, the first architectural choice is whether to build with standalone components or traditional NgModules. Standalone components were introduced in Angular 14 and became the default project style in later releases. NgModules remain supported, but they carry metadata and boilerplate that standalone components remove.
Useful takeaway: choose standalone components for new greenfield applications and most feature libraries. Reserve NgModules for legacy codebases, shared modules that other NgModules import, or third-party libraries whose schematics still require NgModule metadata.
Constraints to check before deciding
- Angular version: standalone components require Angular 14 or later; standalone route APIs and application bootstrap stabilized in later releases.
- Existing module graph: if your app already imports shared NgModules, mixing styles is possible but adds indirection.
- Third-party dependencies: some component libraries and code-generation schematics still expect an NgModule to import.
- Team familiarity: developers accustomed to declaring providers and declarations in @NgModule need to learn direct imports in standalone metadata.
Supported options compared
| Factor | Standalone components | NgModules |
|---|---|---|
| Bootstrapping | bootstrapApplication(AppComponent, appConfig) | platformBrowserDynamic().bootstrapModule(AppModule) |
| Routing | RouterModule.forRoot(routes) with standalone route configs | RouterModule.forChild(routes) inside feature modules |
| Dependencies | imports array lists modules, components, pipes, directives directly | imports, declarations, exports, providers arrays |
| Lazy loading | loadComponent or loadChildren with standalone route | loadChildren pointing to NgModule class |
| Tree-shaking | Unused standalone code can be pruned more readily | Whole module may be retained if any part is imported |
| Migration effort | Native for new projects; incremental conversion possible | Zero for existing module-based apps |
Trade-offs
Standalone components
Advantages: less decorator boilerplate, clearer dependency graph, smaller initial bundle when the build prunes unused components, and a simpler mental model because a component declares exactly what it uses.
Disadvantages: existing shared NgModules must be imported individually into each standalone component that needs them; some Angular Material schematics and third-party libraries still generate NgModule-based code; route configuration syntax differs from module-based lazy loading.
NgModules
Advantages: mature ecosystem compatibility, centralized declaration of providers and shared UI pipelines, and straightforward lazy loading of entire feature modules.
Disadvantages: more boilerplate, risk of importing an entire module when only one component is needed, and slower onboarding for developers learning Angular's current direction.
Implementation: standalone feature route
This example creates a standalone component and registers it in a standalone route configuration. Run commands in the project root of an Angular workspace. You need write permission to the project directory.
1. Create a standalone project
ng new customer-portal --standalone --routing --style=css
cd customer-portal
Placeholder: customer-portal is the application name. Expected result: Angular generates a project with app.config.ts and app.routes.ts instead of an AppModule. Risk: running this inside an existing directory with the same name overwrites or conflicts with existing files.
2. Generate a standalone component
ng generate component orders/order-list --standalone
Expected result: Angular creates order-list.component.ts with standalone: true and an imports array. You must manually add any modules, pipes, or directives the template uses to that array.
3. Configure the route
import { Routes } from '@angular/router';
import { OrderListComponent } from './orders/order-list.component';
export const routes: Routes = [
{
path: 'orders',
loadComponent: () => import('./orders/order-list.component').then(m => m.OrderListComponent)
}
];
loadComponent lazy-loads the standalone component on demand. Unlike NgModule-based lazy loading, there is no module class to reference.
4. Bootstrap the application
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
bootstrapApplication replaces platformBrowserDynamic().bootstrapModule(AppModule). The appConfig object holds providers and route configuration.
Validation
- Run
ng buildin the project root. Confirm the build emits todist/without template compilation errors. - Inspect the generated bundles. Standalone components that are not imported anywhere should not appear in the initial JavaScript bundle, though exact sizes depend on the build configuration and Angular version.
- Run the test suite with
ng testor your configured runner. Confirm that component interactions and route navigation still work after adding standalone routes. - Open the application in a browser and navigate to the lazy-loaded route. The network panel should request the component chunk only after navigation, not on initial load.
Limitations
Standalone components do not automatically remove all NgModule complexity. If your application depends on a shared NgModule that declares dozens of pipes and directives, each standalone component must import those dependencies explicitly. Some third-party libraries package their public API as an NgModule; you can import that NgModule into a standalone component's imports array, but you cannot convert the library itself without a new release from its maintainers.
Practical check: after migration, search the project for @NgModule. If the only remaining NgModule is the root or a third-party import, the standalone migration is substantially complete. If feature modules still declare providers and declarations, those modules are candidates for incremental conversion.
Rollback
Creating a new standalone project does not modify existing code, so there is nothing to roll back. If you convert an existing NgModule-based component to standalone, back up the component and its module first. To revert, restore the @NgModule decorator, move imports back into declarations and providers, and remove standalone: true.
Bottom line: for new Angular applications, standalone components are the supported default and usually the better long-term choice. Keep NgModules only when legacy structure or library compatibility forces the issue.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.