Angular Standalone Components and Their Impact on Modularity

Standalone Components will make NgModules optional. This will not affect the modularity of applications in any way, because one should use libraries for modules and not the NgModule. One of the upcoming features in the Angular framework will be “Standalone Components” (SC) or “Optional NgModules”. It will remove the necessity for NgModules.

There are many blog posts, articles, etc. about SC. This article answers a question that isn’t discussed that often: How will SC affect modularity in an Angular application?

NgModule contains the term module. When SC makes NgModules optional and maybe deprecates them in the long run, does that mean we will not have modules anymore? Given that Angular is an enterprise framework, and the Angular team’s continuous strive for stability, this would be an unexpected move.

I start with a summary of what SCs are and what advantages they bring. Then I focus on the main question, namely if optional NgModules and modularity form a contradiction. The last part is about the best way we can prepare for SC right now.

The source code is available on GitHub.

If you prefer watching over reading, here is the video version:

1. What Are Standalone Components?

Discussions around SC have been going on for several months in the community. Igor Minar, one of the key developers behind Angular, said that he had wanted to deal with NgModules since the very early beta version of Angular. This was in 2016. So, it was quite an event when Pawel Kozlowski Posted the official RFC for Standalone Components on GitHub.

The key element in Angular is the component. Every component belongs to a NgModule which provides the dependencies for it. The property declarations of a NgModule’s decorator create this relationship. For example, if the component requires the formGroup directive, the NgModule supplies that direct via the ReactiveFormsModule.

The same rule applies to the other visual elements which are Pipe and Directive. For the sake of simplicity, these two are included when we talk of a component. This isn’t just extra overhead. Given that additional link between Component and Module and the fact that a NgModule can declare multiple components, it is not so easy to figure out which dependencies a particular component requires.

Angular architectural - flow

Besides components, there are also Services to consider – and three different ways how to provide them. The NgModule can do it, the component can do it, or the Service could provide itself via the providedIn property. The last option is the preferred way and was introduced in Angular 6.

So, we see that even a single component containing a form and a service involves a relatively high level of complexity.

Single component containing a service

Standalone Components remove the additional layer of the NgModule.

A component’s decorator will receive additional properties for that. Providing Services will also become easier since there will be only two options.

2. How to Modularise in Standalone Components?

Do NgModules enable modularity in Angular applications? And if yes, should we now write our applications without modules?

2.1 What Is a Module?

A good definition of a module would be a group of elements in an application that belong together. There are different possibilities for “belonging together”. It could be a group that contains only presentational components, a group that contains all relevant elements for an NgRx feature state, or some other criteria.

The most important functionality of a module is encapsulation. A module can hide certain elements from the outside. Encapsulation is key for a stable architecture because it prevents every element from accessing any other element.

2.2 Is NgModule a Module?

So, is the NgModule a module in that sense? Unfortunately, the NgModule fulfills these requirements only partially. It provides encapsulation at least for the visual elements (Component, Directive, Pipes) but it cannot enforce them. Theoretically, I can create a component that extends from an encapsulated one, create a new selector, and voilĂ . Nothing prevents me from accessing a not exported class.

import { Component } from '@angular/core';
import { EncapsulatedComponent } from './module/encapsulated.component';

@Component({
  selector: 'app-stolen',
  templateUrl: './module/encapsulated.component.html',
})
export class StolenComponent extends EncapsulatedComponent {}

It doesn’t get better with Services. As described above, they can live outside of the control of a NgModule. Since NgModules cannot deliver full modularity, we can already answer the main question of this article:

Standalone Components or Optional Modules will not have an impact on an application’s modularity.

Drawing, we have now a new question: What should we have been using for modules all this time?

2.3 How To Implement Modules in Angular?

There is something else in Angular besides NgModule, but it disguises itself under a different name. It is the library or just lib. Since Angular 6, the Angular CLI supports the generation of libraries.

A library gets its own folder next to the actual app’s folder. The library also has a so-called barrel file index.ts where encapsulation happens. Everything that is exported from that index.ts is exposed to the outside. That everything can be Services, TypeScript Interfaces, functions, or even NgModules.

A sidenote about NgModules in libraries: Until SC are available, we still need the NgModule to expose components. That is why a library includes NgModules as well.

export { Lib1Module } from './lib/lib1.module';
export { ExposedComponent } from './lib/exposed.component';
export { RootProvidedService } from './lib/services/root-provided-service';
export { ExposedService } from './lib/services/exposed.service';

What about enforcing encapsulation? It can happen any time a developer imports a non-exposed file from a library. With a modern IDE that can happen very quickly. We often see this when the non-exposed elements are imported via a relative path whereas the exposed ones are imported by using the library’s name.


Code snippet: enforcing encapsulation.

Unfortunately, there is nothing in the Angular CLI that would prevent us from doing that. Which is where nx steps in. Nx is an extension to the Angular CLI and provides, among many features, a linting rule for modularity. This linting rule throws an error if a so-called deep import, ie direct access to a non-exposed file, happens. See this excellent article for more information.

Nx provides another linting rule where we can also define dependency rules between modules. We can come up with rules like module A can access modules B and C but module B can only access C. These rules are also validated via linting.

So, it is the library (coupled with nx) and not the NgModule, that fulfills the requirements for a module.

3. How Do I Prepare for the Migration?

We don’t have SC yet, but can we prepare for them now to make the migration as smooth as possible? For quite some time, and long before SCs were announced, the pattern Single Component Angular Module or “SCAM” has been popular in the community. With SCAM, a NgModule declares only one component.

If you use SCAM already, the effort to migrate to SC will probably be just moving the imports and providers properties to the @Component decorator. A script can do this task automatically. You can find more information here.

Should you apply SCAM to an existing application? If you have a big application and a big desire to move to SC as quickly as possible, then SCAM can help you achieve that. In general, I would just wait until SC is released.

There is also a shim that provides SC right now. This shim is only for demonstration purposes and is not safe for production.

Summary

Dependency management in Angular comes in different variations. This can potentially reduce consistency and is an obstacle for newcomers. The NgModule, in particular, creates unnecessary overhead. Standalone Components (Optional NgModules) will eliminate NgModules and will be a big improvement.

Optional NgModules will have essentially no impact on modularity provided by libraries. For applications following the SCAM pattern, a script can do the migration automatically. Without SCAM, you will have to do it manually.

.

Leave a Comment