Angular 18’s standalone components are the modern way to build Angular applications. Instead of declaring components in NgModule classes that must track every component, pipe, and directive, standalone components declare their own dependencies directly in the component’s imports array. New Angular 18 projects created with the CLI use standalone architecture by default. Understanding the difference between the two architectures is essential — most existing Angular codebases use NgModules, but new development should use standalone.
Standalone Bootstrap and Configuration
// ── main.ts — standalone bootstrap ────────────────────────────────────────
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch(err => console.error(err));
// ── app.config.ts — application-level providers ───────────────────────────
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { routes } from './app.routes';
import { authInterceptor } from './core/interceptors/auth.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes, withComponentInputBinding()), // route params as @Input
provideHttpClient(withInterceptors([authInterceptor])), // functional interceptors
provideAnimationsAsync(),
],
};
// ── Standalone component ───────────────────────────────────────────────────
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink } from '@angular/router';
import { CommonModule } from '@angular/common';
import { PostListComponent } from './features/posts/post-list.component';
@Component({
selector: 'app-root',
standalone: true, // ← declares this as a standalone component
imports: [ // ← imports dependencies directly (no NgModule needed)
RouterOutlet,
RouterLink,
CommonModule,
PostListComponent, // use another standalone component by importing it
],
template: `
<nav>
<a routerLink="/posts">Posts</a>
</nav>
<router-outlet />
`,
})
export class AppComponent { }
imports: [] directly. The result is self-contained components that are easier to understand (no need to trace through NgModule imports) and easier to test (no TestBed module setup).withComponentInputBinding() when calling provideRouter() to enable passing route parameters as @Input() properties on routed components. Instead of injecting ActivatedRoute and subscribing to params, the component receives the route parameter as a simple input: @Input() id!: string. This is cleaner and more testable — no need to mock ActivatedRoute in unit tests. Angular 18 supports this for route params, query params, and route data.imports array: imports: [MatButtonModule, MatInputModule]. Do not import the module in a non-existent AppModule. Angular Material 15+ provides standalone component APIs (import individual components like MatButton directly), but older versions require the module approach. Check the library version before deciding which import style to use.NgModule vs Standalone Comparison
| Concern | NgModule | Standalone (Angular 18) |
|---|---|---|
| Component declaration | In NgModule.declarations[] | standalone: true on component |
| Dependency import | Module.imports[] → shared by all | Component.imports[] → per-component |
| Bootstrap | platformBrowserDynamic().bootstrapModule(AppModule) | bootstrapApplication(AppComponent, config) |
| Lazy loading | loadChildren: () => import(‘./module’) | loadComponent: () => import(‘./component’) |
| Testing setup | TestBed.configureTestingModule + NgModule | Simpler TestBed setup, no module |
Common Mistakes
Mistake 1 — Forgetting to add a component to imports[] before using it in the template
❌ Wrong — <app-post-list> in template but PostListComponent not in imports[]; NG8001 error.
✅ Correct — always add components, directives, and pipes to the using component’s imports: [] array.
Mistake 2 — Adding standalone: true but also declaring the component in an NgModule
❌ Wrong — standalone component declared in NgModule declarations[]; NG6007 compile error.
✅ Correct — standalone components are never declared in NgModules; they are imported directly.