Skip to main content

Standalone components in Angular

Standalone component is a component that declares its own dependencies directly in @Component, without needing an NgModule to register it. Available since Angular 14, generated by default since Angular 17.

Theory

TL;DR

  • Set standalone: true in @Component and list all template dependencies in the component's own imports array
  • Before Angular 14, every component had to live inside an NgModule - standalone removes that layer entirely
  • Bootstrapping changes: bootstrapApplication() replaces platformBrowserDynamic().bootstrapModule()
  • Lazy loading gets shorter: loadComponent loads one component instead of loadChildren wrapping a whole module
  • Directives and pipes support standalone: true the same way components do

Quick example

typescript
@Component({ selector: 'app-user-card', standalone: true, imports: [CommonModule, RouterLink], // direct imports, no NgModule wrapper template: ` <div *ngIf="user"> <h2>{{ user.name }}</h2> <a [routerLink]="['/profile', user.id]">View profile</a> </div> ` }) export class UserCardComponent { @Input() user?: { id: number; name: string }; }

CommonModule and RouterLink go straight into the component. No module file, no declarations array. The component owns its dependencies.

NgModule vs standalone

In module-based Angular, every component has a module that declares it. That module also imports whatever the component needs (CommonModule, FormsModule, etc.) and re-exports the component so other modules can use it. Three files minimum for a simple feature.

Standalone removes that layer. The imports array in @Component works exactly like the one in @NgModule, but scoped to this one component. When the compiler resolves template bindings, it reads that array directly instead of walking up to find an enclosing module.

When to use standalone

  • New Angular project: the CLI from Angular 17+ generates standalone by default
  • Any new component in an existing app: standalone and module-based code mix freely
  • Single-component lazy loading: loadComponent without a feature module wrapper
  • Shared component libraries: fewer files, clearer dependency graph

Migrating an existing project is possible with ng generate @angular/core:standalone.

Bootstrapping without NgModule

typescript
// main.ts import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; import { provideRouter } from '@angular/router'; import { provideHttpClient } from '@angular/common/http'; import { routes } from './app/app.routes'; bootstrapApplication(AppComponent, { providers: [ provideRouter(routes), provideHttpClient(), ], });

AppModule is gone. Providers that used to live there now go into the second argument of bootstrapApplication. Each provide* function replaces what used to be a module import. I find this pattern much easier to read in code reviews than hunting through a module's imports array.

Lazy loading a single component

typescript
export const routes: Routes = [ { path: 'settings', loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent), }, ];

Before standalone, a lazy route needed its own NgModule with a routing module inside. Now the component itself is the lazy boundary. Smaller bundle split, less code to write.

How the compiler handles standalone

When Angular sees standalone: true, it treats the component's imports as the only source of truth for template resolution. Every directive, pipe, or component used in the template must appear in that list, or inside a module listed there. A missing import throws a compile error, not a runtime crash. That is one concrete improvement over the old module system, where a missing re-export in a shared module would only break things at runtime.

Common mistakes

Forgetting standalone: true while removing NgModule

typescript
// Fails to compile @Component({ selector: 'app-header', // standalone: true is missing imports: [CommonModule], template: `<nav>...</nav>` }) export class HeaderComponent {}

Angular throws: Component 'HeaderComponent' is not a part of any NgModule. The fix is one line.

Using declarations inside a standalone component

typescript
// Wrong - declarations does not exist in standalone @Component({ standalone: true, declarations: [ChildComponent], // not a valid property here }) export class ParentComponent {}

Standalone components use imports for everything. declarations is a module concept only.

Importing too broadly

typescript
// Works, but pulls in everything inside CommonModule imports: [CommonModule] // More precise - tree-shaking can remove what you do not use imports: [NgIf, NgFor, AsyncPipe]

Both compile. The second option gives the bundler more information about what to remove from the final output.

Wrong bootstrap method after removing AppModule

If you delete AppModule but leave platformBrowserDynamic().bootstrapModule(AppModule) in main.ts, the app will not start. Switch to bootstrapApplication(AppComponent, { providers: [...] }).

Where standalone appears in production code

  • Angular 17+ CLI: every generated component is standalone by default
  • Angular Material 15+: all library components ship as standalone
  • Nx workspaces: standalone is the default for Angular libraries
  • Angular's own documentation and tour-of-heroes sample: fully standalone since Angular 17

Follow-up questions

Q: Can a standalone component be used inside a module-based app?
A: Yes. Import it in the imports array of an NgModule, not in declarations. The component handles its own dependencies, so the module only needs to list it.

Q: Can a non-standalone component use a standalone one?
A: Through a module, yes. Import the standalone component into a feature module, then that module can be imported by other modules. It works, but the cleaner path is migrating the parent component to standalone as well.

Q: What happens if you put a non-standalone component in an NgModule's imports?
A: Angular throws a compile error telling you to add standalone: true first. It is not silent.

Q: Is standalone: true required in Angular 17+?
A: Not required. NgModule-based code still compiles and runs. The CLI defaults to standalone, but you can opt out. The Angular team has stated they plan to make standalone the primary API in a future major version, but there is no deprecation date for NgModules yet.

Q: What is the difference between loadComponent and loadChildren in routing?
A: loadChildren loads a module or a standalone routes array. loadComponent loads one specific component as the route entry point. For a feature area with multiple nested routes, use loadChildren pointing to a standalone routes file.

Examples

Reactive form in a standalone component

typescript
import { Component, inject } from '@angular/core'; import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms'; import { NgIf } from '@angular/common'; @Component({ selector: 'app-login-form', standalone: true, imports: [ReactiveFormsModule, NgIf], template: ` <form [formGroup]="form" (ngSubmit)="submit()"> <input formControlName="email" type="email" placeholder="Email" /> <span *ngIf="form.get('email')?.touched && form.get('email')?.invalid"> Enter a valid email </span> <button type="submit" [disabled]="form.invalid">Log in</button> </form> ` }) export class LoginFormComponent { private fb = inject(FormBuilder); form = this.fb.group({ email: ['', [Validators.required, Validators.email]], }); submit() { if (this.form.valid) { console.log(this.form.value); } } }

ReactiveFormsModule is imported at the component level. No AppModule, no shared forms module somewhere up the tree. The component carries what it needs.

Mixing standalone with an existing NgModule

typescript
// Legacy feature module adopting a new standalone component @NgModule({ imports: [ CommonModule, UserCardComponent, // standalone - goes in imports, not declarations ], declarations: [ LegacyDashboardComponent, // not yet migrated ], exports: [LegacyDashboardComponent], }) export class LegacyDashboardModule {}

Standalone components go in imports. Non-standalone components stay in declarations. This is how incremental migration works without rewriting the entire module tree at once.

Standalone directive used directly in a component

typescript
import { Directive, ElementRef, inject, OnInit } from '@angular/core'; @Directive({ selector: '[appAutoFocus]', standalone: true, }) export class AutoFocusDirective implements OnInit { private el = inject(ElementRef); ngOnInit() { this.el.nativeElement.focus(); } } @Component({ selector: 'app-search', standalone: true, imports: [AutoFocusDirective], template: `<input appAutoFocus type="search" placeholder="Search..." />` }) export class SearchComponent {}

AutoFocusDirective is standalone, so it goes directly into the component's imports. No module in the middle. The same pattern applies to standalone pipes.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?