1. Angular’s Future Without NgModules – Part 1: Lightweight Solutions Using Standalone Components
  2. Angular’s Future Without NgModules – Part 2: What Does That Mean for Our Architecture?
  3. 4 Ways to Prepare for Angular’s Upcoming Standalone Components
  4. Routing and Lazy Loading with Angular’s Standalone Components

Angular’s Future Without NgModules – Part 1: Lightweight Solutions Using Standalone Components

Standalone Components make the future of Angular applications more lightweight. We don't need NgModules anymore. Instead, we just use EcmaScript modules.

  1. Angular’s Future Without NgModules – Part 1: Lightweight Solutions Using Standalone Components
  2. Angular’s Future Without NgModules – Part 2: What Does That Mean for Our Architecture?
  3. 4 Ways to Prepare for Angular’s Upcoming Standalone Components
  4. Routing and Lazy Loading with Angular’s Standalone Components

Update on 2022-05-01: Texts and examples fully updated to use initial Standalone Components support in Angular 14.0.0-next.15 (instead of just using a shim for simulating them).

Standalone Components is one of the most exciting new Angular features since quite a time. They allow for working without NgModules and hence are the key for more lightweight and straightforward Angular solutions. A first implementation already landed in Angular 14 BETA and the Angular Team tries hard to make them available until version 14 is released in the first half of 2022.

In this article series, I’m going to demonstrate how to leverage this innovation. For this, I’m using an example application completely written with Standalone Components.

The 📂 source code for this can be found in the form of a traditional Angular CLI workspace (see several branches) and as an Nx workspace that uses libraries as a replacement for NgModules.

A Bit of History: NgModules – Unwanted but Needed

Actually, Angular modules that were already used in AngularJS 1.x should not be implemented at all. Rather, the core team was happy to announce its discontinuation in 2014 when Angular 2 was announced in Paris. In a presentation that will probably go down in the history of the framework, a tombstone was shown for every concept from AngularJS 1.x that was no longer required. The tenor was that all of these concepts had become obsolete: partly because of the new architecture of Angular, partly because certain concepts were already planned for the next iteration of EcmaScript.

It was not planned to implement NgModules for Angular 2

The latter was the case with NgModules, which appeared obsolete due to the module system planned for EcmaScript 2015. The community was all the more surprised when one of the last release candidates appeared with NgModules. The main reason for this was pragmatic: We needed a way to group building blocks that are used together. Not only to increase the convenience for developers, but also for the Angular Compiler whose development lagged a little behind. In the latter case, we are talking about the compilation context. From this context, the compiler learns where the program code is allowed to call which components.

However, the community was never really happy with this decision. Having another modular system besides that of EcmaScript didn’t feel right. In addition, it raised the entry barrier for new Angular developers. That is why the Angular team designed the new Ivy compiler so that the compiled application works without modules at runtime. Each component compiled with Ivy has its own compilation context. Even if that sounds grandiose, this context is just represented by two arrays that refer to adjacent components, directives, and pipes.

Since the old compiler and the associated execution environment have now been permanently removed from Angular as of Angular 13, it was time to anchor this option in Angular’s public API. For some time there has been a design document and an associated RFC [RFC]. Both describe a world where Angular modules are optional. The word optional is important here: Existing code that relies on modules is still supported.

Getting Started With Standalone Components

In general, implementing a Standalone Component is easy. Just set the standalone flag in the Component decorator to true and import everything you want to use:

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

import { NavbarComponent, SidebarComponent } from './shell';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';

@Component({
  standalone: true,
  selector: 'app-root',
  imports: [
    RouterModule,
    CommonModule,

    NavbarComponent,
    SidebarComponent,

    HomeComponent,
  ],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  [...]
}

The imports define the compilation context: all the other building blocks the Standalone Components is allowed to use. For instance, you use it to import further Standalone Component, but also existing NgModules.

The exhaustive listing of all these building blocks makes the component self-sufficient and thus increases its reusability in principle. It also forces us to think about the component’s dependencies. Unfortunately, this task turns out to be extremely monotonous and time consuming.

Therefore, there are considerations to implement a kind of auto-import in the Angular Language Service used by the IDEs. Analogous to the auto-import for TypeScript modules, the IDE of choice could also suggest placing the corresponding entry in the imports array the first time a component, pipe or directive is used in the template.

The Mental Model

The underlying mental model helps to better understand Standalone Components. In general, you can imagine a Standalone Component as a component with its very own NgModule:

Mental Model

This is similar to Lars Nielsen‘s SCAM pattern. However, while SCAM uses an explicit module, here we only talk about a thought one.

While this mental model is useful for understanding Angular’s behavior, it’s also important to see that Angular doesn’t implement Standalone Components that way underneath the covers.

Pipes, Directives, and Services

Analogous to standalone components, there are also standalone pipes and standalone directives. For this purpose, the pipe and directive decorators also get a standalone property. This is what a standalone pipe will look alike:

@Pipe ({
  standalone: true,
  name: 'city',
  pure: true
})
export class CityPipe implements PipeTransform {

  transform (value: string, format: string): string {[…]}

}

And here is an example for a standalone directive:

@Directive ({
    standalone: true,
    selector: 'input [appCity]',
    providers: […]
})
export class CityValidator implements Validator {

    [...]

}

Thanks to tree-shakable providers, on the other hand, services have worked without modules for quite a time. For this purpose the property providedIn has to be used:

@Injectable ({
  providedIn: 'root'
})
export class FlightService {[…]}

In a later part of this article series, we will look more closely to dependency injection in a world of Standalone Components. However, to anticipate one key point already now: Using modern tree-shakable providers instead of old-style providers in NgModules helps a lot when migrating to Standalone Components.

Bootstrapping Standalone Components

Until now, modules were also required for bootstrapping, especially since Angular expected a module with a bootstrap component. Thus, this so called AppModule or "root module" defined the main component alongside its compilation context.

With Standalone Components, it will be possible to bootstrap a single component. For this, Angular provides a method bootstrapApplication which can be used in main.ts:

// main.ts

import { HttpClientModule } from '@angular/common/http';
import { enableProdMode, importProvidersFrom } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app/app.component';
import { APP_ROUTES } from './app/app.routes';
import { TicketsModule } from './app/tickets/tickets.module';

import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

bootstrapApplication(AppComponent, {
  providers: [
    importProvidersFrom(RouterModule.forRoot(APP_ROUTES)),
    importProvidersFrom(HttpClientModule),
    importProvidersFrom(TicketsModule),
  ]
});

The first argument passed to bootstrapApplication is the main component. Here, it’s our AppComponent. Via the second argument, we pass application-wide service providers. These are the providers, you would register with the AppModule when going with NgModules.

The provided helper function importProvidersFrom allows bridge the gap to existing NgModules. The spread operator used here will become optional quite soon.

Please also note, that importProvidersFrom works with both NgModules but also ModuleWithProviders as returned by the Router’s forRoot and forChild methods. While this allows to immediately leverage existing NgModule-based APIs, we will see more and more functions that replace the usage of importProvidersFrom in the future. For instance, to register the router with a given configuration, the Angular team plans to introduce a provideRouter function:

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(APP_ROUTES)
    [...]
  ]
});

Compatibility With Existing Code

As discussed above, according to the mental model, a Standalone Component is just a component with its very own NgModule. This is also the key for the compatibility with existing code still using NgModules.

On the one side, we can import whole NgModules into a Standalone Component:

@Component({
  standalone: true,
  selector: 'app-root',
  imports: [
    RouterModule,
    CommonModule,
    [...]
  ],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  [...]
}

But on the other side, we can also import a Standalone Component (Directive, Pipe) into an existing NgModule:

@NgModule({
  imports: [
    CommonModule,

    // Imported Standalone Component:
    FlightCardComponent,
    [...]
  ],
  declarations: [
    MyTicketsComponent
  ],
  [...]
})
export class TicketsModule { }

Interestingly, standalone components are imported like modules and not declared like classic components. This may be confusing at first glance, but it totally fits the mental model that views a standalone component a component an NgModule.

Also, declaring a traditional component defines a strong whole-part relationship. A traditional component can only be declared by one module and then, it belongs to this module. However, a standalone component doesn’t belong to any NgModule but it can be reused in several places. Hence, using imports here really makes sense.

Interim Conclusion: Standalone Components — and now?

So far we’ve seen how to use Standalone Components to make our Angular applications more lightweight. We’ve also seen that the underlying mental model guarantees compatibility with existing code.

However, now the question arises how this all will influence our application structure and architecture. The next part of this short series will shed some light on this.

» Next Part: Angular’s Future Without NgModules – Part 2: What Does That Mean for Our Architecture?

More on Architecture?

When architecting enterprise-scale Angular applications, several additional questions come in mind:

  • According to which criteria can we sub-divide a huge application into libraries and sub-domains?
  • Which access restrictions make sense?
  • Which proven patterns should we use?
  • How can we evolve our solution towards micro frontends?

Our free eBook (about 100 pages) covers all these questions and more:

free ebook

Feel free to download it here now!

Don't Miss Anything!


Subscribe to our newsletter to get all the information about Angular.


* By subscribing to our newsletter, you agree with our privacy policy.

Unsere Angular-Schulungen

Top Schulungen

Angular Architecture Workshop

Workshop with strategies for your large and long-lasting business applications.

Remote & In-House
3 days or 4 days (depending on time model)
Remote: 20.09. - 23.09.2022
Also available as a company workshop
More Information

weitere Schulungen

  1. Angular’s Future Without NgModules – Part 1: Lightweight Solutions Using Standalone Components
  2. Angular’s Future Without NgModules – Part 2: What Does That Mean for Our Architecture?
  3. 4 Ways to Prepare for Angular’s Upcoming Standalone Components
  4. Routing and Lazy Loading with Angular’s Standalone Components

Current Blog Articles

  1. Angular’s Future Without NgModules – Part 1: Lightweight Solutions Using Standalone Components
  2. Angular’s Future Without NgModules – Part 2: What Does That Mean for Our Architecture?
  3. 4 Ways to Prepare for Angular’s Upcoming Standalone Components
  4. Routing and Lazy Loading with Angular’s Standalone Components

Only One Step Away!

Send us your inquery today - we help you with pleasure!

Jetzt anfragen!