Suggest an editImprove this articleRefine the answer for “Standalone components in Angular”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Standalone component** in Angular (14+) is a component with `standalone: true` in its decorator that manages its own template dependencies through the `imports` array, without being declared in an `NgModule`. ```typescript @Component({ selector: 'app-card', standalone: true, imports: [NgIf], template: `<p *ngIf="title">{{ title }}</p>` }) export class CardComponent { @Input() title = ''; } ``` **Key point:** other components go into `imports`, not `declarations`. Use `bootstrapApplication()` to start a fully standalone app.Shown above the full answer for quick recall.Answer (EN)Image**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.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.