The main benefit of standalone components in Angular is probably not what you think it is.

In this guide we are going to learn everything about standalone components, and how they are better than NgModule-based components.

We are going to lay out in our next sections all the advantages of standalone components, an explain why some of their perceived disadvantages aren't that big of a problem after all.

We are also going to learn how standalone components can help make your application much faster, with minimal refactoring.

We will also look at how you can easily migrate your application to standalone components, and start reaping all these benefits.

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: Looking to learn more about other Angular core features like standalone components? Have a look at my other articles on Angular Core.

Let's first show a couple of standalone components, and then talk about their advantages right after this quick introduction.

What are Standalone Components?

Standalone components are a new type of Angular component that does not need to be declared in a NgModule.

These are components that can be used directly in the template of another component without being part of an NgModule, or imported in an NgModule.

Before diving into the advantages of standalone components, let's first see how they compare with regular components.

Here is a plain regular component, that is not standalone:

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

}

Regular components like this one must be declared in a NgModule, otherwise, they just can't be used in the template of another component.

In this case, we are going to import it into our main application module:

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

}

Now let's turn the HelloComponent into a standalone component!

For that, all we have to do is add the property standalone: true in its @Component decorator:

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

}

And that's it!

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

For example, let's say that we want to use the HelloComponent in the ParentComponent:

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

}

As you can see, we just have to import it in the imports array, and that's it!

These manual imports might seem like they would be cumbersome to maintain, but that is not the case, more on that later.

Notice that without the import, the ParentComponent will not work as intended, and often there won't be an error thrown.

Using standard directives in Standalone Components

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

To use them in standalone components we need to add them to the imports array of the component:

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 have imported the ngClass directive in the imports array of the
StandaloneComponent component so that we can use it in the template.

Without this import, the component wouldn't work as intended.

Again, this manual import could be seen as a nuisance, more on that in the next section.

Why Standalone Components?

As you can see, standalone components are at first sight not that catchy at all!

You remove the concept of NgModule, but replace it with manual imports.

So why are they so much better than regular components?

There are several reasons.

improved developer experience

The main reason why standalone components were introduced in Angular was to remove the notion of NgModules from the developer experience.

The concept of a module in Angular seemed unnecessary and made it a bit harder for beginners to learn Angular from scratch.

It was one extra concept to learn, and it was not clear why it was needed.

To solve this problem, Angular introduced standalone components.

Now with standalone components, you can create your components without having to declare them in any module, which is much more convenient.

Manual imports are not really a problem

When they were first introduced, standalone components came with the drawback that you had to manually import any dependencies that the component needed directly in the component.

So even the core directives like ngClass or ngStyle have to be imported manually in each standalone component, as we have seen.

This was initially seen as problematic because all those manual imports are also a pain to maintain.

After all, one of the advantages of NgModules was that you could import a dependency in only one place inside the module, right?

So for a brief moment, we thought that standalone components would be cumbersome to use at the application level, due to constantly having to import everything, which doesn't sound practical.

It wasn't apparent what tangible benefits standalone components brought to the table when building applications.

So there didn't seem to be a compelling enough reason to migrate existing applications to them.

But nothing could be further from the truth!

Regarding the imports issue, nowadays any IDE can import all the dependencies of a standalone component automatically, thanks to the Angular Language Service.

The manual imports are no longer a pain, and you hardly notice them at all. The whole experience is seamless.

It just works.

So what is the main practical benefit of standalone components?

Removing NgModules is not even the main benefit of standalone components, or the main reason to migrate an established application to them.

For me, the main benefit of standalone components is that they make it super easy to develop a fully lazy-loaded application, or migrate an existing monolithic application and make it fully lazy-loaded.

If you have been using NgModule components for a while, you likely noticed the following:

Your application developed with NgModules is probably relatively monolithic, even though NgModules are supposed to help you modularize your application.

And it's hard to refactor an existing large application to use lazy-loading if you haven't done it enough from the start.

If you did use lazy-loading, you probably only have a few lazy-loaded modules, but each module still contains a lot of screens.

So your application is probably not using lazy-loading to its full potential, because of the overhead caused by having to create a separate module per screen.

But what if I told you that if you switch to standalone components, that will make it trivial for you to take your semi-monolithic application and turn it into a fully lazy-loaded application in no time?!

Even if your application was not built with lazy loading in mind, and even if you have not created enough lazy loaded modules yet, it doesn't matter.

All you have to do is to use the Angular CLI to migrate to standalone components, make a few minor changes in your routing configuration using the new loadComponent option, and voila:

Every single screen of your application is now lazy loaded and is offloaded to a separate screen bundle, and your application is all of a sudden much faster!

So for me, that was the killer feature that made me decide to spend the time and resources to switch everything to standalone components, and my team never looked back.

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

In a nutshell, besides removing extra concepts from the framework and making it more beginner-friendly, I find that:

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 next few sections, we are going to see how to lazy-load standalone components.

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

Standalone Pipes

Just like components, we can also create standalone pipes.

The process is very similar to standalone components, we just have to 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 is an example of a standalone directive:

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

}

To use it, we just have to 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 you can see, the process is very similar to standalone components and pipes.

Notice that so far, we have just been showing examples of how to use standalone components, directives, and pipes inside other standalone components.

But don't worry, if you need to use standalone elements in NgModule-based components, you can do that as well.

There is full interoperability in both directions. Let's have a look at what this interoperability looks like.

Using Standalone Components in NgModule-based Components

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

To use a standalone component within a NgModule-based application, you just need to import the standalone component:

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

As you can see, it's very simple to use standalone components in module-based applications.

But what about the other way around?

Using NgModule-based Components in Standalone Components

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

Let's see how it's done.

First, we create a 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 in 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 within the standalone component's template just like any other component.

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

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

We can easily mix and match the two types of components in our application, without any problem.

Lazy Loading with Standalone Components

Lazy-loading just got a whole lot easier with the introduction of standalone components.

At first, without standalone components, lazy loading was done via modules.

We had to create a module for a set of components, pipes, or directives that we 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 is an example of what the routing configuration of 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 does not come from the routing configuration itself.

It comes from the need to have to create a module and define all it's dependencies, not because we want to encapsulate certain features and make them reusable, but just for the single purpose of lazy-loading that content.

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

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

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

We don't have to create a module per lazy-loaded screen anymore, and import all the dependencies that each screen needs every time.

So wih standalone components, lazy-loading is not a pain anymore, it's now become trival and it's much easier to use.

Application Bootstrapping with Standalone Components

To take full advantage of 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 just need to pass a standalone component that we want to turn into the root of the application.

We can also import any dependencies that we need, like the router, forms, etc. using standalone APIs.

Migrating to Standalone Components

You can migrate your application to standalone components automatically using the Angular CLI.

You may still have to do some minor refactorings, but the CLI will do most of the work for you.

The migration happens in 3 separate steps. I recommend that you do them on a branch and create 3 separate commits.

For each step, you will need to run the following command each time:

ng generate @angular/core:standalone

Here are the 3 steps:

Step 1: Run the migration and select the "Convert all components, directives, and pipes to standalone" option. This will add the standalone flag everywhere, and populate the imports array of each component.

Step 2: Run the migration again, but this time select the "Remove unnecessary NgModule classes" option. This will attempt to remove as many NgModules as possible, but it will likely not be able to remove them all, so you will have to review the code here and manually remove any modules that you can.

Step 3: Run the migration one last time and select the "Bootstrap the application using standalone APIs" option.

This will remove the use of the AppModule to bootstrap the application, and it will use standalone APIs instead.

This last step is critical to be able to benefit the most from standalone components. For example, if you don't do this last step, component-based lazy loding with loadComponent won't work properly.

Test the application after each step, and commit the changes separately so that you can roll back to the previous step if something goes wrong.

In my experience, the migration will go smoothly, but you will likely still have to do some minor changes yourself to fully remove all NgModules from the codebase.

From there, just go to your routing configuration, and everywhere you see the option component, replace it with loadComponent and that's it!

All of a sudden you have gone from a semi-monolithic application to a fully lazy-loaded application, and your application feels way faster.

I hope that you enjoyed this post, to get notified when new similar Angular posts like this are out, I invite you to subscribe to our newsletter:

You will also get up-to-date news on the Angular ecosystem.

If you want a deep dive into all the features of Angular Core like standalone components and others, have a look at the Angular Core Deep Dive Course.

We have a full section dedicated to standalone components, and how to migrate to them:

Summary

Taking the time to migrate your application to standalone components is definitely worth it.

Switching to standalone components is not just about removing one extra concept from your codebase and preparing yourself for future versions of Angular.

Standalone components also provide some immediate very tangible benefits, especially when it comes to making it much easier to systematically use lazy-loading in your application.

The automated Angular CLI migration will do most of the work for you.

The manual imports are not an issue thanks to the Angular Language Service, and the whole developer experience is seamless.

Go ahead and try it out, and let me know in the comments below of any questions you might have!