Angular Standalone Components and their impact on modularity

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 SC 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:

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 an NgModule which provides the dependencies for it. The property declarations of an NgModule's decorator creates this relationship.

For example, if the component requires the formGroup directive, the NgModule supplies that directive 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 an NgModule can declare multiple components,  it is not so easy to figure out which dependencies a particular component requires.

NgModule declaring multiple Components
NgModule declaring multiple Components

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.

Component with NgModule and Service
Component with NgModule and 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.

Standalone Components
Standalone Component

How to modularise Standalone Components?

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

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.

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.

Component extending from an encapsulated Component

It doesn't get better with Services. As described above, they can live outside of the control of an 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.

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

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.

index.ts exposing NgModule

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.

Linting error due to deep-import
Linting error due to deep-import

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, i.e. 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 module B & 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.

How do I prepare best for the migration?

WWe 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 SC were announced, the pattern Single Component Angular Module or "SCAM" has been popular in the community. With SCAM, an 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.


I would like to thank Pawel Kozlowski for reviewing this article and providing valuable feedback.

Further Reading

Leave a Reply