Hook — 05 : 45 a.m., Santa Marta (Colombia) → Cancún code-review
Caribbean dawn blurred my screen when Sofía, dialing from Cancún, shared a PR: “I added a tiny badge component but had to touch three NgModules—did I do it right?” The diff looked like a spaghetti western: declarations, imports, exports sprinkled across files. “Hold on,” I said, cracking open the CLI. We regenerated the badge as a standalone component, deleted 40 lines of module boilerplate, and her build passed in two minutes—before the street vendor below finished frying my arepa de huevo. That beach-side refactor is our roadmap today: you’ll learn why Angular’s standalone APIs matter, how to migrate an existing feature, and where common pitfalls hide—knowledge forged from shipping apps across the Dominican Republic, Brazil, Costa Rica, Panama, Mexico, and (of course) Colombia.
Why Standalone Components Matter Today
Pain point | Classic NgModule workflow | Standalone escape hatch |
---|---|---|
Boilerplate | Declare ➜ import ➜ export in multiple files | One decorator replaces it all |
Onboarding juniors | “Which module do I touch?” Slack pings | Component usable instantly after ng g c |
Bundle bloat | Eagerly pulled shared NgModules | Fine-grained tree-shaking per component |
Lazy loading | Extra routing modules, indirection | Direct loadComponent calls |
Testing friction | Configure TestBed imports per spec | Shallow tests with importProvidersFrom() |
Angular 18 makes standalone the default for ng new
. Learning it now means less refactor later.
What Exactly Is a Standalone Component?
A standalone component is self-describing. Everything it needs—template, styles, providers, and dependencies—lives in its own decorator. No parent NgModule
required.
tsCopyEdit@Component({
standalone: true,
selector: 'surf-badge',
template: `<span class="badge" [ngClass]="color">{{ label }}</span>`,
styles: [`.badge { padding: .25rem .5rem; border-radius: .5rem; }`],
imports: [NgClass], // bring in directives you use
})
export class SurfBadgeComponent {
@Input() label = '';
@Input() color: 'primary' | 'warning' = 'primary';
}
That’s it. Drop <surf-badge>
anywhere after importing the file and Angular’s new tree-shakable build graph handles the rest.
Walkthrough 1 — Creating a Standalone Component from Scratch
bashCopyEditng g component surf-badge --standalone --export
--standalone
addsstandalone: true
&imports
array.--export
(when inside a standalone library) auto-re-exports viapublic-api.ts
.
Use it in AppComponent
:
tsCopyEditimport { SurfBadgeComponent } from './surf-badge/surf-badge.component';
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(RouterModule.forRoot([])),
],
// make it globally available
standaloneComponents: [SurfBadgeComponent],
});
CLI pro-tip: Add
"standalone": true
inangular.json
schematics to make this the new default for your team.
Walkthrough 2 — Migrating an Old NgModule Feature
Imagine dashboard.module.ts
with five components. We’ll convert one—chart-card
.
- Add
standalone: true
tochart-card.component.ts
. - Move imports used only by this component into its decorator:
tsCopyEditimports: [NgIf, NgOptimizedImage, TooltipDirective]
- Delete
declarations
entry indashboard.module.ts
. - Route directly:
tsCopyEditconst routes: Route[] = [
{
path: 'charts',
loadComponent: () =>
import('./charts/chart-card.component').then(m => m.ChartCardComponent),
},
];
- Run
ng build
—Webpack emits a smaller chunk because the previous shared module (and its unused dependencies) no longer load for other routes.
Remote-Work Insight 🏝️
In Costa Rica’s Osa Peninsula our coworking Wi-Fi throttled at lunch. Every kilobyte mattered. Migrating three components to standalone shaved 160 kB from our initial JS bundle—enough to keep FCP under Google’s “good” threshold even when the router rebooted mid-day (which happened more than we’d like).
Common Pitfalls & How to Dodge Them
Symptom | Root cause | Quick fix |
---|---|---|
NG0500: Importing ... which is not a standalone component | Forgot standalone: true on dependency | Add flag or re-export via a standalone library |
Circular imports between standalone components | Shared service imported twice | Create a provideRoot() service or use signals instead |
Global pipes/directives missing | Not added in imports array | Import from @angular/common or shared standalone |
IDE auto-completes wrong selector | Old module still in path | Remove dead NgModule file after migration |
Performance & Accessibility Checkpoints
- Bundle size —
ng build --stats-json
➜ useng build-analyzer
to verify per-component chunks. - First Contentful Paint — should drop as modules shrink.
- Change Detection — standalone components default to
ChangeDetectionStrategy.Default
; set toOnPush
for heavy UI. - ARIA consistency — each component owns its label associations; lint with
@angular-eslint/template-accessibility
.
Hands-On: Standalone + Signals + Lazy Loading
bashCopyEditng g component live-rate --standalone --skip-tests
tsCopyEdit@Component({
standalone: true,
selector: 'live-rate',
template: `<span>{{ rate() | number:'1.2-2' }}</span>`,
imports: [NgIf],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LiveRateComponent {
private _rate = signal(0);
rate = this._rate.asReadonly();
constructor(ws: WebSocketService) {
ws.stream<number>('rate').subscribe(this._rate);
}
}
Lazy route:
tsCopyEdit{
path: 'rates',
loadComponent: () =>
import('./live-rate/live-rate.component').then(m => m.LiveRateComponent),
}
Now only users navigating to /rates
download the component and its WebSocket code—perfect for mobile data plans along Mexico’s Mayan coast.
Call-Out Table — Key Standalone APIs
API | One-liner purpose |
---|---|
standalone: true | Declares component/directive/pipe independent of NgModule |
imports array | Localizes dependencies (directives, pipes, other standalone comps) |
bootstrapApplication() | Boots app without root module |
importProvidersFrom() | Bring NgModules into standalone world (e.g., RouterModule ) |
loadComponent() | Lazy-load standalone component in router |
Essential CLI Commands
Command | What it does |
---|---|
ng new surf-shop --standalone | Workspace defaults to standalone APIs |
ng g c header --standalone --export | Generate reusable header |
ng g interceptor auth --standalone | Create provider w/out module |
ng build --configuration production | Tree-shakes standalone graphs |
SVG Diagram Idea
- Classic: Component ➜ NgModule A ➜ AppModule ➜ Bundle.
- Standalone: Component ➜ Router
loadComponent
➜ Bundle (smaller, direct).
Arrows show fewer hops and chunk splits.
Wrap-Up
Standalone components free Angular devs from the ceremony that once pushed newcomers toward other frameworks. Generate, import, ship—it’s that simple. Start by flipping one leaf component, then a feature, and soon your entire codebase breathes easier, builds faster, and welcomes juniors without NgModule anxiety. Whether you’re coding from Colombian balconies or Panamanian coworks, that’s a win.
Drop questions or solo-component victories below—I’ll reply between layovers and late-night arepa sessions.