Performance

Angular Standalone Components: Complete Guide

A complete guide to Angular standalone components. Learn why they are way better than regular components, and how to easily upgrade to them.

Angular Standalone Components: Complete Guide — Performance article by Angular University on Angular In Depth
Angular Standalone Components: Complete Guide — Performance article by Angular University on Angular In Depth
On this page · 15 sections

The most significant advantage of standalone components in Angular is likely not what you expect.

Throughout this guide, we will cover everything there is to know about standalone components and why they outperform NgModule-based components.

In the upcoming sections, we will outline all the benefits of standalone components and clarify why some of their apparent drawbacks are not as problematic as they seem.

We will also explore how standalone components can significantly boost your application's performance with minimal effort.

Additionally, we will examine how to seamlessly transition your current application to standalone components and immediately enjoy these advantages.

Table of Contents

  • What are standalone components?
  • Using standard directives in standalone components
  • Why Standalone Components?
  • So what is the main practical benefit of standalone components?
  • Standalone Pipes
  • Standalone Directives
  • Using standalone components in NgModule-based components
  • Using NgModule-based components in standalone components
  • Lazy loading with standalone components
  • Application bootstrapping with standalone components
  • Easily migrating to standalone components
  • Summary

Note: Interested in diving deeper into other Angular core features like standalone components? Check out my other articles on Angular Core.

Let's start by presenting a couple of standalone components, and we will discuss their advantages right after this brief introduction.

What are Standalone Components?

Standalone components represent a new kind of Angular component that doesn't require declaration within an NgModule.

These components can be used directly in another component's template without being part of or imported into an NgModule.

Before we dive into the benefits, let's first see how they stack up against regular components.

Here is a typical regular component that is not standalone:

@Component({
  selector: "hello",
  template: `Hello {{ name }}`,
})
class HelloComponent {

}

Regular components like this one must be declared in an NgModule; otherwise, they cannot be used in another component's template.

In this example, we are importing it into our main application module:

@NgModule({
  declarations: [AppComponent, HelloComponent],
  imports: [BrowserModule, FormsModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {

}

Now, let's convert the HelloComponent into a standalone component!

To achieve this, all we need to do is add the property standalone: true to its @Component decorator:

@Component({
  selector: "hello",
  template: `Hello {{ name }}`,
  standalone: true,
})
class HelloComponent {

}

And that's it!

Now, to use this component, we simply import it into the component where we intend to use it.

For instance, suppose we want to use HelloComponent inside the ParentComponent:

@Component({
  selector: "parent",
  template: `<hello></hello>`,
  imports: [HelloComponent],
})
class ParentComponent {

}

As you can see, we only need to add it to the imports array, and we're done!

These manual imports might seem annoying to maintain, but that's not the case; we'll discuss that shortly.

Note that without the import, ParentComponent won't function as expected, and often no error will be thrown.

Using standard directives in Standalone Components

Angular has made most of its built-in directives available as standalone versions.

To use them within standalone components, we need to list them in the component's imports array:

import { NgClass } from "@angular/core";

@Component({
  selector: "app-hello",
  template: ` <div [ngClass]="{ highlight: true }">Hello World!</div> `,
  imports: [NgClass],
  standalone: true,
})
class StandaloneComponent {

}

Here, we've included the ngClass directive in the imports array of the StandaloneComponent so that we can reference it in the template.

Without this import, the component would not behave correctly.

Once again, this manual import could be viewed as an inconvenience, but we'll address that in the next section.

Why Standalone Components?

At first glance, standalone components might not seem all that impressive!

You eliminate the NgModule concept but trade it for manual imports.

So why are they considered superior to regular components?

There are several reasons.

improved developer experience

The primary motivation for introducing standalone components in Angular was to strip away the NgModule concept from the developer experience.

The module concept in Angular felt superfluous and made it more difficult for newcomers to grasp Angular from scratch.

It was an additional concept to understand, and its necessity wasn't always clear.

To address this, Angular launched standalone components.

Now, with standalone components, you can build your components without declaring them in any module, which is far more convenient.

Manual imports are not really a problem

When standalone components were first released, they came with the caveat that you had to import all dependencies directly into each component.

This meant even core directives like ngClass or ngStyle needed explicit imports in every standalone component, as we've demonstrated.

This was initially viewed as a drawback because managing all those imports seemed burdensome.

After all, one benefit of NgModules was the ability to import a dependency in just one place within the module, right?

For a moment, it seemed like standalone components would be impractical at the application level due to the constant need to import everything, which didn't sound efficient.

It wasn't obvious what tangible benefits standalone components offered when building real-world applications.

This made the case for migrating existing applications less compelling.

However, nothing could be further from the truth!

Concerning the imports issue, modern IDEs can automatically import all dependencies for a standalone component, thanks to the Angular Language Service.

Manual imports are no longer a hassle, and you barely notice them. The entire experience is fluid.

It simply works.

So what is the main practical benefit of standalone components?

Eliminating NgModules isn't even the primary benefit of standalone components, nor is it the main reason to migrate an established application.

For me, the standout benefit of standalone components is that they make it incredibly easy to build a fully lazy-loaded application, or refactor an existing monolithic one to be fully lazy-loaded.

If you've worked with NgModule components for some time, you've probably noticed the following:

Your NgModule-based application is likely relatively monolithic, even though NgModules are meant to help modularize your codebase.

Refactoring a large existing application to use lazy-loading is challenging unless you've incorporated it from the beginning.

If you did use lazy-loading, you likely have only a few lazy-loaded modules, but each one still contains numerous screens.

So your application isn't leveraging lazy-loading to its fullest potential, due to the overhead of creating a separate module for each screen.

But what if I told you that switching to standalone components would make it effortless for you to turn your semi-monolithic application into a fully lazy-loaded one in no time?!

Even if your application wasn't originally built with lazy loading in mind, and even if you haven't created enough lazy-loaded modules yet, it doesn't matter.

All you need to do is use the Angular CLI to migrate to standalone components, tweak a few things in your routing configuration using the new loadComponent option, and voila:

Every screen in your application is now lazy-loaded and separated into its own bundle, making your application dramatically faster!

For me, that was the decisive feature that convinced me to invest the time and resources to migrate everything to standalone components, and my team has never looked back.

Our main application bundle was cut by more than half, thanks to a refactoring that took less than a day for a fairly large application, and every single screen is now lazy-loaded.

In short, besides removing extra concepts from the framework and making it more approachable for beginners, I believe:

The main benefit of standalone components is that they make it trivial to develop a fully lazy-loaded application, or migrate an existing application and make it fully lazy-loaded.

In the following sections, we'll see how to lazy-load standalone components.

But before that, let's explore what other constructs, besides components, can be made standalone.

Standalone Pipes

Just like components, pipes can also be standalone.

The approach is very similar to standalone components; we just add the standalone flag:

@Pipe({
  name: "capitalise",
  standalone: true,
})
export class CapitalisePipe implements PipeTransform {
  transform(word: string): string {
    return word.toLocaleUpperCase();
  }
}

This pipe is now standalone, so all we have to do is import it into the component where we want to use it:

@Component({
  selector: "app-hello",
  template: `Hello {{ name | capitalise }}`,
  standalone: true,
  imports: [CapitalisePipe],
})
class AppComponent {

}

And just like pipes, we can also create standalone directives.

Standalone Directives

Here's an example of a standalone directive:

@Directive({
  selector: "[example-directive]",
  standalone: true,
})
class ExampleDirective {

}

To use it, we simply import it into another component:

@Component({
  selector: "app-hello",
  template: `
  <div example-directive>Hello {{ name | capitalise }}</div>`,
  standalone: true,
  imports: [CapitalisePipe, ExampleDirective],
})
class StandaloneComponent {

}

As demonstrated, the process is very similar to that of standalone components and pipes.

So far, we've only shown examples of using standalone components, directives, and pipes within other standalone components.

But don't worry, you can also use standalone elements in NgModule-based components.

There is full interoperability in both directions. Let's examine what this interoperability entails.

Using Standalone Components in NgModule-based Components

Standalone components can be used in NgModule-based components just like any other component.

To use a standalone component in an NgModule-based application, all you need is to import the standalone component:

@NgModule({
  declarations: [
    AppComponent,
    StandaloneComponent
  ],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {
}

As you can see, incorporating standalone components into module-based applications is straightforward.

But what about the reverse scenario?

Using NgModule-based Components in Standalone Components

We can also easily use NgModule-based components within a standalone component.

Let's walk through the steps.

First, we create an NgModule and export the NgModule-based component:

@NgModule({
  declarations: [TraditionalComponent],
  // export the NgModule-based component
  exports: [TraditionalComponent], 
})
export class MyModule {

}

We then import the NgModule into the standalone component:

@Component({
  selector: "app-standalone",
  template: `I'm a standalone component 😁`,
  standalone: true,
  // Import the NgModule that declares the component
  imports: [MyModule], 
})
class StandaloneComponent {

}

Now, the NgModule-based components can be used in the standalone component's template just like any other component.

Note that because we imported the entire MyModule, any other components that are part of the imported module's public API can also be used in the standalone component.

As you can see, the interoperability between Ng-Module and standalone components is seamless.

We can effortlessly mix and match both types of components in our application without any issues.

Lazy Loading with Standalone Components

Lazy-loading has become significantly easier with the advent of standalone components.

Previously, without standalone components, lazy loading was accomplished through modules.

You had to create a module for a set of components, pipes, or directives that you wanted to lazy load.

This was time-consuming and involved a lot of boilerplate:

  • creating the lazy loaded module
  • import all the dependencies manually
  • configure the module in the router

Here's an example of what the routing configuration for a lazy-loaded module looks like:

const routes: Routes = [
  {
    path: "one",
    loadChildren: () =>
      import("./module-one/moduleone.module").
      then((m) => m.ModuleOneModule),
  },
];

The overhead of lazy-loading modules doesn't stem from the routing configuration itself.

It comes from the necessity to create a module and define all its dependencies—not because we want to encapsulate features for reuse, but solely for the purpose of lazy-loading that content.

With standalone components, on the other hand, lazy loading is much simpler:

export const ROUTES: Route[] = [
  {
    path: "lazy-hello",
    loadComponent: () =>
      import("./app-hello")
      .then((m) => m.StandaloneComponent),
  },
];

All we need to do now is use the loadComponent option and point to the top-level container component of the route, and that's it!

We no longer need to create a module for each lazy-loaded screen or re-import all the dependencies each screen requires.

So with standalone components, lazy-loading is no longer a hassle; it's now become trivial and much easier to implement.

Application Bootstrapping with Standalone Components

To fully leverage standalone components, it's recommended to bootstrap the application using standalone APIs instead of NgModules:

import { bootstrapApplication } 
 from "@angular/platform-browser";

bootstrapApplication(StandaloneComponent);

In the bootstrapApplication API, we simply pass a standalone component that we want to designate as the application's root.

We can also import any necessary dependencies, such as the router, forms, etc., using standalone APIs.

Transitioning Your Codebase to Standalone Components

The Angular CLI provides a straightforward path for migrating an existing application to standalone components.

While you'll likely need to handle a few manual adjustments, the bulk of the transformation is automated.

This migration consists of three distinct phases. It's advisable to perform these steps on a dedicated branch, creating a separate commit for each phase.

You'll execute the same command for each phase:

ng generate @angular/core:standalone

Here is a breakdown of the three phases:

Phase 1: Initiate the migration and choose the option to convert every component, directive, and pipe to standalone. This action adds the necessary standalone flag and populates the imports array for each entity.

Phase 2: Execute the migration once more, this time opting for the "Remove unnecessary NgModule classes" selection. This process attempts to eliminate as many NgModules as possible. However, it's unlikely to catch everything, so you'll need to inspect the resulting code and manually eliminate any remaining modules.

Phase 3: Run the migration a final time, choosing the "Bootstrap the application using standalone APIs" option.

This step retires the AppModule for bootstrapping and relies on standalone APIs instead.

Completing this last phase is essential for reaping the full advantages of standalone components. For instance, without it, component-based lazy loading with loadComponent won't function correctly.

Verify that your application works as expected after each phase, and make separate commits so you can easily revert to a previous state if you encounter problems.

Based on my experience, the migration proceeds smoothly, though you should anticipate the need for some minor manual edits to fully purge NgModules from your codebase.

Once that's complete, navigate to your routing configuration and replace every instance of the component option with loadComponent.

Now, your application is no longer a monolith; it has become fully lazy-loaded, and the performance improvement is immediately noticeable.

We hope this guide has been helpful. To stay informed about future Angular tutorials, we invite you to subscribe to our newsletter:

Subscribers also receive the latest updates on the Angular ecosystem.

For a comprehensive look at Angular Core features, including standalone components, consider exploring the Angular Core Deep Dive Course.

This course dedicates an entire section to standalone components and the migration process:

Angular Standalone Components: Complete Guide — figure 1

Final Thoughts

Investing effort in migrating to standalone components is a worthwhile endeavor.

This shift isn't solely about removing a concept from your code or preparing for upcoming Angular versions.

Standalone components offer significant, immediate benefits, particularly in streamlining the adoption of lazy-loading throughout your entire application.

The Angular CLI's automated migration handles the heavy lifting.

Manual imports aren't a hassle, thanks to the Angular Language Service, ensuring a smooth developer experience.

Give it a try and let us know if you have any questions in the comments section below!

AU
Angular University

Writes about RxJS, Components, Signals. Active 2015–2026.

All 79 articles →