Angular 21 makes standalone components the default. If you've worked with NgModules before, standalone feels like removing scaffolding that was never really necessary — your component just declares what it needs, directly.
What Changed from NgModules
With NgModules, every component needed to be declared in a module before it could be used anywhere:
// Old NgModule approach
@NgModule({
declarations: [NavbarComponent, UserCardComponent],
imports: [CommonModule, RouterModule],
exports: [NavbarComponent]
})
export class SharedModule {}
With standalone components, the component itself is the unit of reuse — no module needed:
// Angular 21 standalone approach
@Component({
selector: 'app-navbar',
standalone: true,
imports: [RouterLink, NgIf, NgClass],
templateUrl: './navbar.component.html'
})
export class NavbarComponent {}
Your First Standalone Component
// src/app/components/user-card/user-card.component.ts
import { Component, Input } from '@angular/core'
import { NgIf, NgClass } from '@angular/common'
@Component({
selector: 'app-user-card',
standalone: true,
imports: [NgIf, NgClass],
template: `
<div class="card border-0 shadow-sm" [ngClass]="{ 'border-danger': isAdmin }">
<div class="card-body d-flex align-items-center gap-3 p-4">
<div class="rounded-circle text-white fw-bold d-flex align-items-center justify-content-center"
style="width:48px;height:48px;background:#fd4766;font-size:0.85rem;flex-shrink:0;">
{{ initials }}
</div>
<div>
<h6 class="fw-bold mb-0">{{ name }}</h6>
<p class="text-muted small mb-0">{{ email }}</p>
<span *ngIf="isAdmin" class="badge text-white mt-1" style="background:#fd4766;">Admin</span>
</div>
</div>
</div>
`
})
export class UserCardComponent {
@Input() name = ''
@Input() email = ''
@Input() isAdmin = false
get initials(): string {
return this.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
}
}
Using it in another standalone component — just add it to imports:
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [UserCardComponent, NgFor],
template: `
<div class="container py-4">
<h1 class="fw-bold mb-4">Team</h1>
<div class="row g-3">
<div class="col-md-6" *ngFor="let user of users">
<app-user-card
[name]="user.name"
[email]="user.email"
[isAdmin]="user.isAdmin"
/>
</div>
</div>
</div>
`
})
export class DashboardComponent {
users = [
{ name: 'Gagan Singh', email: 'gagan@lettstartdesign.com', isAdmin: true },
{ name: 'Rahul Kumar', email: 'rahul@company.com', isAdmin: false },
{ name: 'Priya Sharma', email: 'priya@company.com', isAdmin: false },
]
}
Bootstrapping Without AppModule
main.ts looks like this in Angular 21:
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser'
import { provideRouter } from '@angular/router'
import { provideHttpClient } from '@angular/common/http'
import { AppComponent } from './app/app.component'
import { routes } from './app/app.routes'
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
provideHttpClient(),
]
}).catch(err => console.error(err))
No AppModule, no BrowserModule declaration. Providers like router and HTTP client are registered directly.
Common Imports Reference
Instead of importing CommonModule for everything, import only what you need:
| Directive | Import from |
|---|---|
*ngIf / @if | NgIf from @angular/common |
*ngFor / @for | NgFor from @angular/common |
[ngClass] | NgClass from @angular/common |
[ngStyle] | NgStyle from @angular/common |
routerLink | RouterLink from @angular/router |
routerLinkActive | RouterLinkActive from @angular/router |
<router-outlet> | RouterOutlet from @angular/router |
async pipe | AsyncPipe from @angular/common |
| All of the above | CommonModule from @angular/common |
Importing individually is better for tree-shaking — your final bundle only includes directives the component actually uses.
New Control Flow Syntax (Angular 17+)
Angular 21 prefers the @if / @for / @switch block syntax over structural directives. These don't need any imports at all:
@Component({
selector: 'app-user-list',
standalone: true,
// No NgIf or NgFor needed — @if and @for are built-in
template: `
@if (users.length > 0) {
<div class="list-group">
@for (user of users; track user.id) {
<div class="list-group-item d-flex justify-content-between">
<span>{{ user.name }}</span>
@if (user.isAdmin) {
<span class="badge text-white" style="background:#fd4766;">Admin</span>
}
</div>
}
</div>
} @else {
<p class="text-muted">No users found.</p>
}
`
})
export class UserListComponent {
users: { id: number; name: string; isAdmin: boolean }[] = []
}
Migrating an Existing NgModule Component
If you're moving a project from NgModules to standalone:
// Before — declared in a module
@Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html'
})
export class SidebarComponent {}
// In some-module.ts
@NgModule({
declarations: [SidebarComponent],
imports: [CommonModule, RouterModule],
exports: [SidebarComponent]
})
export class SomeModule {}
// After — fully standalone
@Component({
selector: 'app-sidebar',
standalone: true,
imports: [NgFor, NgIf, RouterLink, RouterLinkActive],
templateUrl: './sidebar.component.html'
})
export class SidebarComponent {}
Steps for each component:
- Add
standalone: trueto@Component - Add any directives/pipes/components it uses to
imports: [] - Remove it from the
declarationsarray of whichever module had it - Remove it from
exportsin that module too - Any component that uses this one now imports it directly
Angular CLI has a schematic that can do this automatically: ng generate @angular/core:standalone.
Frequently Asked Questions
Need a Full Bootstrap 5 Admin Dashboard?
Get a complete Angular 21 + Bootstrap 5 dashboard with 50+ components — built by the same team behind BootstrapPlanet.
Browse Templates →Use code FIRST30 for 30% off your first purchase.