Standalone Components
Understand how standalone components directly manage their template dependencies, application providers, routing, lazy loading, testing, and interoperability without requiring NgModule declarations.
Lesson content
What is a Standalone Component? A standalone component is an Angular component that can be imported and used directly without being declared inside an NgModule . It declares its template dependencies through the imports property of the @Component() decorator. Simple Definition Standalone Component = Component + Direct Template Imports - NgModule Declaration Basic Example import { Component } from '@angular/core'; @Component({ selector: 'app-welcome', template: ` <h2> Welcome to Angular </h2> ` }) export class WelcomeComponent {} Modern Angular components are standalone by default. For clarity or compatibility with older projects, you may still see: @Component({ selector: 'app-welcome', standalone: true, template: ` <h2>Welcome</h2> ` }) export class WelcomeComponent {} Why Use Standalone Components? Reduce NgModule boilerplate Make dependencies explicit Simplify feature organization Improve lazy loading Make components easier to reuse Support incremental migration Keep application configuration centralized Standalone Component Flow Component Template ↓ Requires Dependencies ↓ Component imports Array ↓ Components Directives Pipes NgModules ↓ Available in Template Standalone vs NgModule Component Standalone Component NgModule Component Imported directly Declared in an NgModule Uses its own imports Receives template dependencies from its module Can be lazy-loaded directly Often lazy-loaded through a feature module Works with bootstrapApplication() Can work with bootstrapModule() Recommended for new development Common in existing applications Importing Another Standalone Component Child Component import { Component, input } from '@angular/core'; @Component({ selector: 'app-user-card', template: ` <article> <h3>{{ name() }}</h3> </article> ` }) export class UserCardComponent { name = input.required<string>(); } Parent Component import { Component } from '@angular/core'; import { UserCardComponent } from './user-card.component'; @Component({ selector: 'app-dashboard', imports: [ UserCardComponent ], template: ` <h2>Dashboard</h2> <app-user-card name="Ajay" /> ` }) export class DashboardComponent {} Import Flow DashboardComponent ↓ imports UserCardComponent ↓ <app-user-card> becomes available ↓ Child component renders Importing Directives and Pipes A standalone component imports every directive and pipe used by its template. import { AsyncPipe, CurrencyPipe } from '@angular/common'; import { Component } from '@angular/core'; import { RouterLink } from '@angular/router'; @Component({ selector: 'app-product', imports: [ AsyncPipe, CurrencyPipe, RouterLink ], template: ` <a routerLink="/products"> Products </a> @if (product$ | async; as product) { <p> {{ product.price | currency:'USD' }} </p> } ` }) export class ProductComponent { product$ = this.productService.getProduct(); } Using Built-In Control Flow Modern built-in control flow does not require importing NgIf , NgFor , or CommonModule . @Component({ selector: 'app-users', template: ` @if (users.length) { @for ( user of users; track user.id ) { <p>{{ user.name }}</p> } } @else { <p>No users found</p> } ` }) export class UsersComponent { users = [ { id: 1, name: 'Ajay' } ]; } Importing CommonModule A standalone component can import CommonModule when several traditional common directives and pipes are required. import { CommonModule } from '@angular/common'; @Component({ selector: 'app-orders', imports: [ CommonModule ], template: ` <div *ngIf="orders.length"> <p *ngFor="let order of orders" > {{ order.date | date }} </p> </div> ` }) export class OrdersComponent { orders = []; } For more explicit dependencies, import only the individual directives and pipes that the template uses. Importing an Existing NgModule Standalone components can import existing NgModules. import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; @Component({ selector: 'app-name-editor', imports: [ FormsModule ], template: ` <input [(ngModel)]="name" /> <p>{{ name }}</p> ` }) export class NameEditorComponent { name = 'Ajay'; } This interoperability allows existing module-based applications to adopt standalone features incrementally. Bootstrapping a Standalone Application Use bootstrapApplication() to start an application with a standalone root component. Root Component import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; @Component({ selector: 'app-root', imports: [ RouterOutlet ], template: ` <h1>Angular Application</h1> <router-outlet /> ` }) export class AppComponent {} main.ts import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; import { appConfig } from './app/app.config'; bootstrapApplication( AppComponent, appConfig ).catch(error => console.error(error) ); Bootstrap Flow main.ts ↓ bootstrapApplication() ↓ Standalone Root Component ↓ Application Providers ↓ Application Starts Application Configuration Application-wide providers are commonly registered in an ApplicationConfig object. import { ApplicationConfig } from '@angular/core'; import { provideHttpClient } from '@angular/common/http'; import { provideRouter } from '@angular/router'; export const appConfig: ApplicationConfig = { providers: [ provideHttpClient(), provideRouter(routes) ] }; Common Provider Functions Provider Function Purpose provideRouter() Configures Angular Router provideHttpClient() Configures HttpClient provideAnimationsAsync() Configures asynchronous animation support where required provideClientHydration() Configures client hydration for server-rendered applications provideZoneChangeDetection() Configures zone-based change-detection options Standalone Routing Standalone components can be used directly in route configuration. import { Routes } from '@angular/router'; import { HomeComponent } from './home.component'; export const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'users', component: UsersComponent } ]; Lazy Loading a Standalone Component Use loadComponent to lazy-load a standalone component. export const routes: Routes = [ { path: 'reports', loadComponent: () => import( './reports/reports.component' ).then( module => module.ReportsComponent ) } ]; Lazy Component Flow User Opens /reports ↓ Router Matches Route ↓ Reports Component Downloaded ↓ Component Created ↓ Page Displayed Lazy Loading Feature Routes A standalone feature can expose a route configuration without creating an NgModule. Feature Routes import { Routes } from '@angular/router'; export const USER_ROUTES: Routes = [ { path: '', loadComponent: () => import( './user-list.component' ).then( module => module.UserListComponent ) }, { path: ':id', loadComponent: () => import( './user-detail.component' ).then( module => module.UserDetailComponent ) } ]; Application Routes export const routes: Routes = [ { path: 'users', loadChildren: () => import( './users/user.routes' ).then( module => module.USER_ROUTES ) } ]; Route-Level Providers Feature-specific services can be scoped to a route and its children. export const routes: Routes = [ { path: 'admin', providers: [ AdminStateService ], loadChildren: () => import( './admin/admin.routes' ).then( module => module.ADMIN_ROUTES ) } ]; Provider Scope Admin Route ↓ Admin Environment Injector ↓ AdminStateService Instance ↓ Available to Admin Children Component-Level Providers A standalone component can provide dependencies to itself and its descendants. @Component({ selector: 'app-editor', providers: [ DraftService ], template: ` <p>Editor</p> ` }) export class EditorComponent { private draftService = inject(DraftService); } Each component instance receives a service instance from its component injector. Using importProvidersFrom() Use importProvidersFrom() when a standalone application needs providers exposed by an existing NgModule. import { importProvidersFrom } from '@angular/core'; import { SomeLegacyModule } from './some-legacy.module'; export const appConfig: ApplicationConfig = { providers: [ importProvidersFrom( SomeLegacyModule ) ] }; Use provider functions instead when the library offers a modern provider API. Standalone Directives and Pipes The standalone pattern applies to components, directives, and pipes. Standalone Directive import { Directive } from '@angular/core'; @Directive({ selector: '[appHighlight]', host: { 'style.backgroundColor': '"yellow"' } }) export class HighlightDirective {} Standalone Pipe import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'initials' }) export class InitialsPipe implements PipeTransform { transform( name: string ): string { return name .split(' ') .map( part => part[0] ) .join('') .toUpperCase(); } } Import into a Component @Component({ selector: 'app-profile', imports: [ HighlightDirective, InitialsPipe ], template: ` <p appHighlight> {{ fullName | initials }} </p> ` }) export class ProfileComponent { fullName = 'Ajay Singh Rajpoot'; } Exporting Standalone Features Standalone components do not require an NgModule export before another component can use them. Consumers import the component directly from its TypeScript export. export { UserCardComponent } from './user-card.component'; export { UserListComponent } from './user-list.component'; Library Public API projects/ └── user-library/ └── src/ ├── public-api.ts └── lib/ ├── user-card.component.ts └── user-list.component.ts Feature Organization Standalone does not mean that all code should be placed in one file. Organize code by business feature. users/ │ ├── components/ │ ├── user-card.component.ts │ └── user-list.component.ts │ ├── services/ │ └── user.service.ts │ ├── models/ │ └── user.model.ts │ ├── user.routes.ts └── user.store.ts Testing Standalone Components Standalone components are imported into the testing module instead of declared. import { ComponentFixture, TestBed } from '@angular/core/testing'; describe( 'UserCardComponent', () => { let fixture: ComponentFixture<UserCardComponent>; beforeEach( async () => { await TestBed .configureTestingModule({ imports: [ UserCardComponent ] }) .compileComponents(); fixture = TestBed.createComponent( UserCardComponent ); } ); it( 'creates the component', () => { expect( fixture.componentInstance ).toBeTruthy(); } ); } ); Testing Rule Standalone Component → TestBed imports Non-Standalone Component → TestBed declarations Overriding Standalone Imports in Tests Tests can override component metadata when a dependency should be replaced with a stub. await TestBed .configureTestingModule({ imports: [ DashboardComponent ] }) .overrideComponent( DashboardComponent, { remove: { imports: [ UserCardComponent ] }, add: { imports: [ UserCardStubComponent ] } } ) .compileComponents(); Using a Standalone Component in an NgModule An existing NgModule imports a standalone component rather than declaring it. import { NgModule } from '@angular/core'; @NgModule({ imports: [ UserCardComponent ], exports: [ UserCardComponent ] }) export class UserFeatureModule {} Important Rule Standalone Component → imports Non-Standalone Component → declarations Using an NgModule Component in Standalone Code When a non-standalone component belongs to an NgModule, import that NgModule into the standalone component. @Component({ selector: 'app-page', imports: [ LegacyWidgetModule ], template: ` <legacy-widget /> ` }) export class PageComponent {} Standalone Migration Existing Angular applications can migrate incrementally. Migration Command ng generate @angular/core:standalone Migration Stages 1. Convert declarations to standalone 2. Remove unnecessary NgModules 3. Switch to standalone bootstrap Typical Migration Flow NgModule Application ↓ Convert Components, Directives and Pipes ↓ Move Dependencies to imports ↓ Remove Safe NgModules ↓ Use bootstrapApplication() ↓ Standalone Application Manual Migration Example Before @Component({ selector: 'app-profile', standalone: false, template: ` <p *ngIf="user"> {{ user.name }} </p> ` }) export class ProfileComponent {} @NgModule({ declarations: [ ProfileComponent ], imports: [ CommonModule ], exports: [ ProfileComponent ] }) export class ProfileModule {} After import { Component } from '@angular/core'; @Component({ selector: 'app-profile', template: ` @if (user) { <p>{{ user.name }}</p> } ` }) export class ProfileComponent {} The component no longer requires an NgModule declaration. Standalone Does Not Mean Singleton The word standalone describes how a component is imported and configured. It does not mean that Angular creates only one instance of the component. <app-user-card /> <app-user-card /> Angular creates two component instances. Standalone Does Not Mean Independent A standalone component can still depend on: Other components Directives Pipes Services Routes Forms HTTP APIs Existing NgModules Standalone means its dependencies are declared directly rather than inherited through an NgModule declaration context. Common Mistakes 1. Declaring a Standalone Component in an NgModule Avoid: @NgModule({ declarations: [ UserCardComponent ] }) Prefer: @NgModule({ imports: [ UserCardComponent ] }) 2. Forgetting Template Imports A selector, directive, or pipe is not automatically available to a standalone template. @Component({ imports: [ RouterLink, UserCardComponent ] }) 3. Adding Everything to Every Component Import only the dependencies used by the component template. 4. Providing Global Services Repeatedly Do not add an application-wide service to many component provider arrays unless separate instances are intentional. 5. Confusing imports with TypeScript Imports A TypeScript import makes a class available in the current file. The component metadata imports array makes that dependency available to the template. TypeScript import = Class Available in File @Component imports = Dependency Available in Template 6. Removing NgModules Without Checking Providers An existing NgModule may register providers or configuration. Move those providers before removing the module. 7. Creating Large Standalone Components Standalone architecture does not remove the need for small, focused components and clear feature boundaries. Best Practices Use standalone components for new Angular development Organize standalone code by business feature Import only template dependencies that are actually used Use built-in control flow for standard conditions and loops Use provider functions for application-level framework configuration Use route-level providers for feature-scoped dependencies Use loadComponent for lazy standalone pages Use loadChildren for lazy route configurations Keep components small and focused Migrate existing applications incrementally Verify providers before removing old NgModules Import standalone components in tests instead of declaring them Interview Questions What is a standalone component? A standalone component is a component that can be imported directly without being declared in an NgModule. How does a standalone component receive template dependencies? It lists components, directives, pipes, or NgModules in its imports metadata property. Are Angular components standalone by default? Yes. In modern Angular, components are standalone by default unless they explicitly specify standalone: false . How do you bootstrap a standalone application? Use bootstrapApplication() with a standalone root component and optional application configuration. Can a standalone component import an NgModule? Yes. Standalone components can import existing NgModules such as FormsModule or a legacy feature module. Can an NgModule use a standalone component? Yes. The NgModule adds the standalone component to imports , not declarations . How do you lazy-load a standalone component? Use the router's loadComponent property. Where are global providers registered? They are commonly registered through ApplicationConfig and passed to bootstrapApplication() . Does standalone mean the component has no dependencies? No. It means dependencies are imported directly rather than supplied through an NgModule declaration context. How do you test a standalone component? Add it to the imports array of TestBed.configureTestingModule() . How do you migrate an existing application? Use the standalone migration schematic or migrate incrementally by converting declarations, moving dependencies into component imports, removing unnecessary modules, and switching application bootstrap. Key Point A standalone component owns its template dependencies directly and can be imported, routed, tested, and lazy-loaded without requiring an NgModule declaration. Easy Formula Standalone Component = @Component + Direct imports + No NgModule declaration Application Startup = bootstrapApplication() + Root Component + ApplicationConfig Lazy Page = Route + loadComponent() Lazy Feature = Route + loadChildren() Global Configuration = ApplicationConfig + Provider Functions Template Needs Dependency ↓ Import Dependency ↓ Add to Component imports ↓ Use in Template