Standalone Components in Angular and How to use that

In this article we are going to discuss what is standalone component and how to use that in angular. When we create component in angular then each component has to be declared in an NgModule. You build a new component, then you go to app.module.ts, add it to the declarations array, maybe add some imports there too. It works but it's a lot of back and forth for something that should be simple.

Angular 14 introduced Standalone Components and Angular 15 made them stable. Angular 17 went a step further and made standalone the default when you generate new projects. So if you're starting a new Angular project today or upgrading an existing one, this is something you need to understand.

The short version — standalone components don't need NgModule at all. Everything the component needs is imported directly inside the component itself. Less boilerplate, cleaner code, easier to understand what a component depends on.

This tutorial shows how to:

  • Understand what standalone components are and why they exist
  • Create a standalone component from scratch
  • Import other components, directives, and pipes directly
  • Bootstrap an Angular app without AppModule
  • Migrate an existing NgModule-based component to standalone
  • Use standalone components with routing

Why Standalone Components

NgModule made sense when Angular was designed. It gave you a way to group related components, services, and imports together. But over time it became more of a headache than a helper.

Think about it — you create a new component, Angular CLI generates it, and your first task is going to app.module.ts to declare it. Then you need CommonModule for *ngIf and *ngFor, FormsModule if you have a form, ReactiveFormsModule for reactive forms — all of it has to be imported in the module, not in the component. So when you're reading a component file, you can't tell what it depends on just by looking at it. You have to go check the module.

Standalone components fix this. All imports live inside the component. You read the component file, you see exactly what it uses. No module hunting.


Step 1 : Create a Standalone Component

When you generate a component using Angular CLI in a project that's Angular 17+, it's standalone by default. But if you're on an older project, you can still create standalone components manually.

Generate a component :

ng generate component user-profile --standalone

Or just add standalone: true manually in the @Component decorator :

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-user-profile',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div>
      <h2>{{ name }}</h2>
      <p *ngIf="bio">{{ bio }}</p>
    </div>
  `
})
export class UserProfileComponent {
  name = 'Amit Gautam';
  bio = 'Angular developer and blogger';
}

That's it. No module needed. The component imports CommonModule directly because it uses *ngIf in the template. Everything this component needs is right here in this file.


Step 2 : Import What You Need Directly

This is the main difference from module-based components. Instead of adding imports to a module, you add them directly to the component's imports array.

Here's a component with a reactive form — normally you'd need ReactiveFormsModule in AppModule. With standalone you just import it in the component :

import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-login-form',
  standalone: true,
  imports: [ReactiveFormsModule, CommonModule],
  template: `
    <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
      <input formControlName="email" placeholder="Email" />
      <span *ngIf="loginForm.get('email')?.invalid && loginForm.get('email')?.touched">
        Email is required
      </span>

      <input formControlName="password" type="password" placeholder="Password" />

      <button type="submit" [disabled]="loginForm.invalid">Login</button>
    </form>
  `
})
export class LoginFormComponent {
  loginForm = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', Validators.required)
  });

  onSubmit() {
    if (this.loginForm.valid) {
      console.log(this.loginForm.value);
    }
  }
}

ReactiveFormsModule is right there in the imports array. You don't need to touch any module file. The component is completely self-contained.


Step 3 : Use One Standalone Component Inside Another

With NgModule, if you wanted to use ComponentA inside ComponentB, ComponentA had to be declared in the same module (or exported from another module that was imported). A lot of boilerplate for something simple.

With standalone, you just import the component directly :

import { Component } from '@angular/core';
import { UserProfileComponent } from '../user-profile/user-profile.component';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [CommonModule, UserProfileComponent],
  template: `
    <div class="dashboard">
      <h1>Dashboard</h1>
      <app-user-profile />

      <div *ngFor="let item of items">
        <p>{{ item }}</p>
      </div>
    </div>
  `
})
export class DashboardComponent {
  items = ['Sales Report', 'User Analytics', 'Revenue Chart'];
}

UserProfileComponent is imported directly into DashboardComponent. No module needed anywhere. This is much more intuitive — you see the import at the top and you know exactly which components are used inside the template.


Step 4 : Bootstrap Angular App Without AppModule

If you're starting a fresh Angular 17+ project, there's no AppModule at all. The bootstrapping happens in main.ts using bootstrapApplication() :

import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent)
  .catch(err => console.error(err));

AppComponent itself is a standalone component :

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `
    <router-outlet />
  `
})
export class AppComponent {}

RouterOutlet is imported directly in AppComponent. No AppModule, no BrowserModule in a module — just the component and what it needs.

If you need to provide services or configure the router at the app level, you pass providers to bootstrapApplication() :

import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    provideHttpClient()
  ]
}).catch(err => console.error(err));

This is how you configure routing and HTTP without AppModule. provideRouter() takes your routes array. provideHttpClient() sets up HttpClient. Clean and explicit.


Step 5 : Use Standalone Components with Routing

In Angular 17+ routing, you can lazy load standalone components directly without needing a module wrapper :

// app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: '',
    loadComponent: () =>
      import('./home/home.component').then(m => m.HomeComponent)
  },
  {
    path: 'dashboard',
    loadComponent: () =>
      import('./dashboard/dashboard.component').then(m => m.DashboardComponent)
  },
  {
    path: 'profile',
    loadComponent: () =>
      import('./profile/profile.component').then(m => m.ProfileComponent)
  }
];

loadComponent() is the standalone equivalent of loadChildren(). Each route lazy loads just the component it needs. No module file sitting in the middle just to export one component.

This actually improves bundle splitting too. Each component gets its own chunk and Angular loads it only when that route is visited.


Step 6 : Migrate Existing Component to Standalone

Got an existing NgModule-based component and want to convert it? Here's how.

Say you have this module-based component :

// OLD - module based
@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html'
})
export class ProductListComponent {
  products = ['Laptop', 'Phone', 'Tablet'];
}

And it's declared in a module that imports CommonModule and HttpClientModule.

Convert it like this :

// NEW - standalone
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { HttpClientModule } from '@angular/common/http';

@Component({
  selector: 'app-product-list',
  standalone: true,
  imports: [CommonModule, HttpClientModule],
  template: `
    <ul>
      <li *ngFor="let product of products">{{ product }}</li>
    </ul>
  `
})
export class ProductListComponent {
  products = ['Laptop', 'Phone', 'Tablet'];

  constructor(private http: HttpClient) {}
}

Then remove ProductListComponent from the declarations array in the module. If no other components need the module after that, you can delete the module file entirely.

Angular also has a migration schematic that can do this automatically for you :

ng generate @angular/core:standalone

This runs through your project and converts components to standalone one by one. It asks you what to migrate — components only, routes, or remove NgModules entirely. Useful for large codebases where doing it manually would take too long.


Common Questions

Can standalone and module-based components work together in the same project?

Yes. You can have both in the same project. Standalone components can be imported into NgModules and vice versa. So you don't have to migrate everything at once.

Do I still need CommonModule?

In Angular 17+, common directives like *ngIf, *ngFor, and async pipe are also available as individual imports. So instead of importing all of CommonModule you can import just NgIf, NgFor separately :

import { NgIf, NgFor } from '@angular/common';

@Component({
  standalone: true,
  imports: [NgIf, NgFor],
  ...
})

Slightly better for tree-shaking. Though importing CommonModule still works fine.

What about services?

Services with providedIn: 'root' work exactly the same. No change needed. Services that were provided in a module need to either move to providedIn: 'root' or be provided via the providers array in bootstrapApplication().


Summary

You learned what Angular Standalone Components are and how to use them. You covered :

  • Why standalone components exist and what problem they solve
  • Creating a standalone component with standalone: true in the decorator
  • Importing CommonModule, ReactiveFormsModule and other modules directly in the component
  • Using one standalone component inside another via the imports array
  • Bootstrapping an Angular app without AppModule using bootstrapApplication()
  • Lazy loading standalone components in routing with loadComponent()
  • Migrating existing module-based components to standalone manually and with the CLI schematic

If you're starting a new Angular project today, go standalone from the beginning. Less boilerplate, clearer dependencies, better lazy loading. And if you have an existing project, the migration schematic makes the move easier than doing it by hand.

I hope you like this article...

Happy coding! 🚀

Post a Comment

0 Comments