From NgModules to Standalone Components
The shift from module-based to standalone component architecture
CLI Migration Script
Overview
Standalone components represent one of the most significant architectural changes in Angular's history. They eliminate the need for NgModules in most cases, making Angular applications simpler to write, understand, and maintain.
The Evolution
All components required declaration in an NgModule.
Standalone components introduced as opt-in feature.
Standalone becomes stable and recommended for new projects.
standalone: true is now the default for new components.
Code Comparison
Basic Component
import { Component } from '@angular/core';
@Component({ selector: 'app-greeting', template: `<h1>Hello, {{ name }}!</h1>`})export class GreetingComponent { name = 'World';}
// app.module.tsimport { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { GreetingComponent } from './greeting.component';
@NgModule({ declarations: [GreetingComponent], imports: [BrowserModule], bootstrap: [GreetingComponent]})export class AppModule {}import { Component } from '@angular/core';
@Component({ selector: 'app-greeting', standalone: true, template: `<h1>Hello, {{ name }}!</h1>`})export class GreetingComponent { name = 'World';}
// main.tsimport { bootstrapApplication } from '@angular/platform-browser';import { GreetingComponent } from './greeting.component';
bootstrapApplication(GreetingComponent);Using Other Components
@Component({ selector: 'app-user-card', template: `<div>{{ user.name }}</div>`})export class UserCardComponent { @Input() user!: User;}
// user-list.component.ts@Component({ selector: 'app-user-list', template: ` <app-user-card *ngFor="let user of users" [user]="user"> </app-user-card> `})export class UserListComponent { users: User[] = [];}
// users.module.ts@NgModule({ declarations: [UserCardComponent, UserListComponent], imports: [CommonModule], exports: [UserListComponent]})export class UsersModule {}@Component({ selector: 'app-user-card', standalone: true, template: `<div>{{ user.name }}</div>`})export class UserCardComponent { user = input.required<User>();}
// user-list.component.ts@Component({ selector: 'app-user-list', standalone: true, imports: [UserCardComponent], template: ` @for (user of users; track user.id) { <app-user-card [user]="user" /> } `})export class UserListComponent { users: User[] = [];}Application Bootstrap
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';import { AppModule } from './app/app.module';
platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err));
// app.module.ts@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, HttpClientModule, RouterModule.forRoot(routes) ], providers: [], bootstrap: [AppComponent]})export class AppModule {}import { bootstrapApplication } from '@angular/platform-browser';import { provideHttpClient } from '@angular/common/http';import { provideRouter } from '@angular/router';import { AppComponent } from './app/app.component';import { routes } from './app/app.routes';
bootstrapApplication(AppComponent, { providers: [ provideHttpClient(), provideRouter(routes) ]});Lazy Loading
const routes: Routes = [ { path: 'admin', loadChildren: () => import('./admin/admin.module') .then(m => m.AdminModule) }];
// admin.module.ts@NgModule({ declarations: [AdminComponent, DashboardComponent], imports: [ CommonModule, AdminRoutingModule ]})export class AdminModule {}export const routes: Routes = [ { path: 'admin', loadComponent: () => import('./admin/admin.component') .then(c => c.AdminComponent) }, { path: 'admin', loadChildren: () => import('./admin/admin.routes') .then(r => r.adminRoutes) }];Key Differences
Benefits
Simpler mental model, no NgModule juggling
Better tree shaking, import only what you use
Clearer dependencies per component
No module configuration in tests
Faster compilation, less metadata
Easier for new developers
Benefits of Standalone
- Simpler Mental Model: No more wondering which module to put things in
- Better Tree Shaking: Only import what you use
- Faster Compilation: Less metadata to process
- Clearer Dependencies: Component imports show exactly what's needed
- Easier Testing: No module configuration required
Gradual Migration
You don't need to convert everything at once! Standalone and NgModule-based components can coexist. Standalone components can import modules, and modules can import standalone components.
Provider Configuration
With standalone, providers are configured at the application level using provide* functions:
bootstrapApplication(AppComponent, { providers: [ provideRouter(routes, withComponentInputBinding()), provideHttpClient(withInterceptors([authInterceptor])), provideAnimations(), // Custom providers { provide: API_URL, useValue: 'https://api.example.com' } ]});Common Provider Functions
| Function | Replaces |
|----------|----------|
| provideRouter() | RouterModule.forRoot() |
| provideHttpClient() | HttpClientModule |
| provideAnimations() | BrowserAnimationsModule |
| provideNoopAnimations() | NoopAnimationsModule |
From NgModules to Standalone Components
The shift from module-based to standalone component architecture
CLI Migration Script
Overview
Standalone components represent one of the most significant architectural changes in Angular's history. They eliminate the need for NgModules in most cases, making Angular applications simpler to write, understand, and maintain.
The Evolution
All components required declaration in an NgModule.
Standalone components introduced as opt-in feature.
Standalone becomes stable and recommended for new projects.
standalone: true is now the default for new components.
Code Comparison
Basic Component
import { Component } from '@angular/core';
@Component({ selector: 'app-greeting', template: `<h1>Hello, {{ name }}!</h1>`})export class GreetingComponent { name = 'World';}
// app.module.tsimport { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { GreetingComponent } from './greeting.component';
@NgModule({ declarations: [GreetingComponent], imports: [BrowserModule], bootstrap: [GreetingComponent]})export class AppModule {}import { Component } from '@angular/core';
@Component({ selector: 'app-greeting', standalone: true, template: `<h1>Hello, {{ name }}!</h1>`})export class GreetingComponent { name = 'World';}
// main.tsimport { bootstrapApplication } from '@angular/platform-browser';import { GreetingComponent } from './greeting.component';
bootstrapApplication(GreetingComponent);Using Other Components
@Component({ selector: 'app-user-card', template: `<div>{{ user.name }}</div>`})export class UserCardComponent { @Input() user!: User;}
// user-list.component.ts@Component({ selector: 'app-user-list', template: ` <app-user-card *ngFor="let user of users" [user]="user"> </app-user-card> `})export class UserListComponent { users: User[] = [];}
// users.module.ts@NgModule({ declarations: [UserCardComponent, UserListComponent], imports: [CommonModule], exports: [UserListComponent]})export class UsersModule {}@Component({ selector: 'app-user-card', standalone: true, template: `<div>{{ user.name }}</div>`})export class UserCardComponent { user = input.required<User>();}
// user-list.component.ts@Component({ selector: 'app-user-list', standalone: true, imports: [UserCardComponent], template: ` @for (user of users; track user.id) { <app-user-card [user]="user" /> } `})export class UserListComponent { users: User[] = [];}Application Bootstrap
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';import { AppModule } from './app/app.module';
platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err));
// app.module.ts@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, HttpClientModule, RouterModule.forRoot(routes) ], providers: [], bootstrap: [AppComponent]})export class AppModule {}import { bootstrapApplication } from '@angular/platform-browser';import { provideHttpClient } from '@angular/common/http';import { provideRouter } from '@angular/router';import { AppComponent } from './app/app.component';import { routes } from './app/app.routes';
bootstrapApplication(AppComponent, { providers: [ provideHttpClient(), provideRouter(routes) ]});Lazy Loading
const routes: Routes = [ { path: 'admin', loadChildren: () => import('./admin/admin.module') .then(m => m.AdminModule) }];
// admin.module.ts@NgModule({ declarations: [AdminComponent, DashboardComponent], imports: [ CommonModule, AdminRoutingModule ]})export class AdminModule {}export const routes: Routes = [ { path: 'admin', loadComponent: () => import('./admin/admin.component') .then(c => c.AdminComponent) }, { path: 'admin', loadChildren: () => import('./admin/admin.routes') .then(r => r.adminRoutes) }];Key Differences
Benefits
Simpler mental model, no NgModule juggling
Better tree shaking, import only what you use
Clearer dependencies per component
No module configuration in tests
Faster compilation, less metadata
Easier for new developers
Benefits of Standalone
- Simpler Mental Model: No more wondering which module to put things in
- Better Tree Shaking: Only import what you use
- Faster Compilation: Less metadata to process
- Clearer Dependencies: Component imports show exactly what's needed
- Easier Testing: No module configuration required
Gradual Migration
You don't need to convert everything at once! Standalone and NgModule-based components can coexist. Standalone components can import modules, and modules can import standalone components.
Provider Configuration
With standalone, providers are configured at the application level using provide* functions:
bootstrapApplication(AppComponent, { providers: [ provideRouter(routes, withComponentInputBinding()), provideHttpClient(withInterceptors([authInterceptor])), provideAnimations(), // Custom providers { provide: API_URL, useValue: 'https://api.example.com' } ]});Common Provider Functions
| Function | Replaces |
|----------|----------|
| provideRouter() | RouterModule.forRoot() |
| provideHttpClient() | HttpClientModule |
| provideAnimations() | BrowserAnimationsModule |
| provideNoopAnimations() | NoopAnimationsModule |