Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Complete] RFC: Standalone components, directives and pipes - making Angular's NgModules optional#43784

Locked
Discussion options

Author: Pawel Kozlowski
Contributors: Alex Rickabaugh, Andrew Kushnir, Igor Minar, Minko Gechev, Pete Bacon Darwin
Area: Angular Framework
Posted: October 8, 2021
Status: Complete -outcome summary linked here.

The goal of this RFC is to validate the design with the community, solicit feedback on open questions, and enable experimentation via a non-production-ready prototype included in this proposal.

Motivation

NgModule is currently one of the core concepts in Angular. Developers new to Angular need to learn about this concept before creating even the simplest possible "Hello, World" application.

More importantly,NgModule acts as a "unit of reasoning and reuse":

  • libraries publishNgModules
  • lazy-loading is centered aroundNgModule, etc.

Given this central role ofNgModule in Angular it is hard to reason about components, directives and pipes in isolation.

Dynamic component creation example

The following example, which dynamically renders a component, contains a subtle, yet critical, problem and is not guaranteed to work at runtime as a result!:

import{Component,ViewContainerRef}from'@angular/core';import{UserViewComponent}from'./business-logic';@Component({...})classDynamicUserView{constructor(privateviewContainerRef:ViewContainerRef){}renderUserView():void{this.viewContainerRef.createComponent(UserViewComponent);}}

SupposeUserViewComponent is authored like this:

@Component({...})exportclassUserViewComponent{constructor(readonlyservice:UserViewService){}}@NgModule({declarations:[UserViewComponent],imports:[/* dependencies here */],providers:[{provide:UserViewService,useClass:BackendUserViewService}],})exportclassUserViewModule{}

UserViewComponent here assumes it will be able to injectUserViewService. This assumption is usually safe because in ordinary usage, users who want to useUserViewComponent don’t depend on it directly, but instead addUserViewModule to theirNgModule.imports.UserViewModule brings with it the provider needed forUserViewService, and so the component will work just fine.

Attempting to instantiateUserViewComponent directly, however, risks violating this assumption. If the application hasn’t independently importedUserViewModule somewhere in itsNgModule hierarchy, the needed provider won’t be available at runtime, and dynamic creation will fail.

Some components do not have such dependencies — they don’t rely on configuration provided in theirNgModule — and can be used directly in this manner. Many components, however, do rely on the context provided by theirNgModule, either its providers, or by expecting certain other components or directives to also be present in the same template.

Components need NgModules

This may seem like an implementation detail of specific components, but in fact it illustrates a fundamental property of the framework:NgModules are the smallest reusable building blocks in Angular, not components.

Angular is one of the only web frameworks where components are not the “units of reuse”.

Having Angular conceptually centered aroundNgModule has a significant impact on the developer experience:

  • authoring components is more involved than coding a class and a template, since:
    • the component might need to be in its ownNgModule, if it’s meant to be reused independently, or
    • the author must fit the component somewhere else in the application’sNgModule hierarchy.
  • APIs around loading and rendering components are:
    • unnecessarily complex, e.g.bootstrapModule() vsbootstrapComponent(), or
    • easy to misuse, like in theViewContainerRef.createComponent() example above.
  • reading component code isn’t sufficient to understand the component behavior:
    • a reader must track down the component’sNgModule to understand the component’s dependencies.
  • Angular’s tooling must deal with the “implicit” dependencies of components on theirNgModule context:
    • this negatively affects both build performance and the optimizability of our generated code.

Main benefits of this proposal

Move Angular in a direction where components, directives, and pipes play a more central role, are self-contained and can be safely imported / used directly.

  • simplifies the mental model of Angular
  • makes new APIs for using components and directives possible (such as fine-grained lazy loading)
  • improves the ability of Angular tooling to process code efficiently.

The mental model shift is the main motivation of this proposal, but there are additional benefits of the reduced conceptual surface (fewer things to learn) and API surface (less code to write).

All these benefits combined should make Angular:

  • simpler to use,
  • easier to reason about,
  • less verbose to write, and
  • faster to compile (more details in#43165).

Goals and non-goals

Goals

  • Shift Angular towards a simpler reuse model that isn’t centered aroundNgModule:
    • allow for a simpler model where components, directives and pipes are self-contained and can be consumed directly;
    • make it possible to introduce new APIs around more dynamic usages of components, directives, pipes;
    • ensure that Angular code written in this style is easier to read and reason about;
    • ensure that Angular code written in this style is more easily processed and optimized via tooling.
  • Improve the developer experience:
    • new Angular users don’t encounterNgModule.declarations until much later in their education;
    • allow components, directives and pipes to be written without needing accompanyingNgModules, reducing the amount of code that needs to be written for typical development scenarios;
    • enable applications where theNgModule concept and API is not needed at all, and as such doesn't need to be learned / mastered.
  • Minimize impact on the Angular ecosystem:
    • overall mental model: developers should not have to learn a new set of rules to reason about their applications using standalone components, directives and pipes;
    • "don't break the World": existing libraries should work as-is without any additional changes;
    • existing documentation and training materials should not become invalid as the result of this proposal;
    • interoperability: standalone components should be able to use existing libraries and standalone components should be usable in existingNgModule-based applications.
  • A neutral impact on performance metrics:
    • code size: applications written with standalone components should not be any larger than theirNgModule-based counterparts;
    • runtime performance: applications using standalone components should not be slower as compared to theirNgModule-based counterparts;
    • compilation time should not increase with the adoption of standalone components — on the contrary, we expect to see improved incremental compilation times for applications opting into standalone components, directives and pipes.

Non-Goals

This proposal is not trying to remove the concept of aNgModule from Angular — it is rather making it optional for typical application development tasks.

At the same time we believe that it paves the path towards greatly reducing the role ofNgModule for typical development scenarios — to the point that some time in the future it would be possible and reasonable for us to consider removing it altogether.

Proposal

Current state

In Angular today, developers useNgModules to manage dependencies. When one component needs to make use of another component, directive, pipe or a provider (whether from within the same application, or from a third-party library on NPM) the dependency is not referenced directly. Instead, anNgModule is imported, which contains exported components, directives and pipes as well as configured providers.

Depending on things indirectly via an importedNgModule introduces subtle assumptions:

  • configuration of the required dependency injection (DI) tokens: because the application is required to import theNgModule in order to use the component, directive or pipe, any providers declared within thatNgModule are guaranteed to be available for injection. If the application were somehow able to skip theNgModule and depend on a component directly, there is no guarantee that the DI system would be correctly configured and be able to instantiate the component;

  • declarations of collaborating directives: a directive may require other directives to also match where it is used, even if the end user isn’t aware of their existence.

Collaborating directives example

When theNgModel directive matches on an<input> element like so:

<inputtype="text"[(ngModel)]="twoWayBoundExpr">

it also expects the collaboratingDefaultControlValueAccessor directive to match on the same<input> DOM element (through theinput selector).

TheFormsModule (which exportsNgModel) also exportsDefaultControlValueAccessor.

Both theNgModel and theDefaultControlValueAccessor directives must be active on the element for[(ngModel)] to function properly.

Most Forms users are entirely unaware of this mechanism.

Generally speaking components, directives or pipes declared in aNgModuleassume presence of a certain context (DI tokens and collaborating directives). AnNgModule specifies this context.

Standalone components, directives, and pipes

A standalone directive, component, or pipe is not declared in any existingNgModule, and:

  • directly manages its own dependencies (instead of having them managed by anNgModule);
  • can be depended upon directly, without the need for an intermediateNgModule.

Thestandalone flag is used to mark the component, directive or pipe as "standalone". It is a property of a metadata object of the relevant decorator (@Component,@Directive, or@Pipe).

ℹ️ Adding thestandalone flag is a signal that components, directives, or pipes are independently usable. Such components, directives, or pipes don't depend on any "intermediate context" of aNgModule.

Simple example

Let's examine a simple example:

import{Component}from'@angular/core';@Component({standalone:true,template:`I'm a standalone component!`})exportclassHelloStandaloneComponent{}

InHelloStandaloneComponent, thestandalone: true flag marks the component as standalone. This makes it obvious to the reader and the tooling that this component is self-contained:

  • it does not depend on any "hidden context";
  • it can be used directly, and
  • it cannot be declared in anyNgModule.

Dependencies example

Since a standalone component has no association with anNgModule, we need a different mechanism of specifying template dependencies. Theimports property on the decorator specifies the component's template dependencies — those directives, components, and pipes that can be used within its template:

import{Component}from'@angular/core';import{FooComponent,BarDirective,BazPipe}from'./template-deps';@Component({standalone:true,imports:[FooComponent,BarDirective,BazPipe],template:`    <foo-cmp></foo-cmp>    <div bar>{{expr | baz}}</div>  `})exportclassExampleStandaloneComponent{}

Interop withNgModule examples

Standalone components, directives and pipes can be imported by other standalone components, as well as byNgModules:

@NgModule({declarations:[AppComponent],imports:[ExampleStandaloneComponent],})exportclassAppModule{}

Here,AppComponent (which is declared inAppModule and thus has its template managed byAppModule) is given visibility ofExampleStandaloneComponent via the import inAppModule.

Conversely, standalone components can also import existingNgModules:

@Component({standalone:true,imports:[CommonModule],template:`    <div *ngFor="let user of users$ | async">{{user.name}}</div>  `})exportclassExampleStandaloneComponent{}

In this example,ExampleStandaloneComponent uses theNgForOf directive and theAsyncPipe, both of which are made available by importingCommonModule. This ability of importing existingNgModules is very important for the interoperability story — it assures that the large ecosystem of existingNgModules is usable as-is from standalone components.

Ways to reason about standalone components

⚠️ Experienced Angular developers might find it easier to reason about the design by using one of the analogies toNgModule described in this section. If you are new toAngular or theNgModule concept you can safely skip this part of the RFC and go directly to the"Use-cases and code examples" part.

VirtualNgModule mental model

A standalone component, directive, or pipe can be considered as being self-declaring. They behave as if there was anNgModule which declared (and exported) the component in question (and only this one component). In practice thisNgModule doesn't exist (or is not made accessible to developers), and can be thought of as "virtual".

Considering an example standalone component:

@Component({standalone:true,imports:[CommonModule],template:`    <ng-template [ngIf]="show">        I'm shown!    </ng-template>  `})exportclassExampleStandaloneComponent{    @Inputshow;}

This component will behave as if it was declared and exported from a "virtual"NgModule:

@NgModule({declarations:[ExampleStandaloneComponent],imports:[CommonModule],exports:[ExampleStandaloneComponent]})exportclassExampleStandaloneComponent{}

Please note that we are using the same name (ExampleStandaloneComponent) to indicate that a standalone component takes on some responsibilities of a@NgModule. It is also a hint that we willnot generate a "virtual"@NgModule class in the final implementation.

As previously mentioned, this "virtual"NgModule is not accessible to developers, and theExampleStandaloneComponent class can be used in its place throughout Angular.

SCAM pattern mental model

Another way of thinking about standalone components, directives and pipes is using the analogy of a single-component Angular module (so-calledSCAM pattern popularized by@LayZeeDK). With this proposal anNgModule for a single component, directive, or pipe does not have to be written by a developer — it is "natively supported" by the framework.

⚠️ The "virtual"NgModule or the SCAM pattern is just a "thinking tool" to help us reason about the design described in this RFC. For performance and maintainability reasons, the actual implementation of this proposal will very likely not end up generating or using "virtual" / SCAMNgModules.

Declarations

Declaring a standalone component, directive or pipe in anNgModule is an error reported at compilation time.

ℹ️ VirtualNgModule analogy:
A standalone component, directive or pipe was already declared in its own "virtual"NgModule and it is not possible to declare a component, directive or pipe in 2 differentNgModules.

Imports and schemas

Since a standalone component takes on some responsibilities of aNgModule we need to extend the list of the properties available in the@Component decorator. More formally we add the following properties with the same syntax and semantics as if placed in an@NgModule:

Theimports andschemas properties on the@Component annotation are allowed only in the presence of thestandalone: true flag. The compiler will report an error ifimports orschemas property is present without the associatedstandalone: true flag.

ℹ️ VirtualNgModule analogy:
Importing a standalone component/directive/pipe into either another standalone component, or into anNgModule, behaves as if its virtualNgModule was imported instead. This means that the single exported standalone component, directive or pipe is added to the compilation scope of the importingNgModule.

Providers from importedNgModules

ExistingNgModules imported into a standalone component might contain providers.

The providers of allNgModules imported (directly or transitively) into a standalone component are "rolled up" and made available to otherNgModules or standalone components that import it in turn.

This "rolling up" of providers continues until we reach a top-levelNgModule (typically the applicationNgModule but potentially a lazy-loadedNgModule).

This is actually how providers are handled in theNgModule imports graph today: Providers are not scoped to an instance of aNgModule but rather are instantiated by an injector representing an accumulated set of all providers from the entire imports graph.

ℹ️ VirtualNgModule analogy:

The collection of providers can be illustrated on the following drawing:

The mechanism is equivalent to how providers are interpreted when traversing theNgModule imports graph today - the only difference here is that the imports graph can contain a mix of "real"NgModule (hand-written by Angular developers) and "virtual" ones (representing standalone components, directives or pipes).

Componentproviders

Unlike providers that are "rolled up" from importedNgModules, providers declared via theproviders property on a standalone component keep the same semantics as a non-standalone component.

In practice this means such providers are defined on the node injector associated with the host node of the component and not anNgModule or top level application injector.

Instead, to ensure a provider is added to a top level injector, a standalone component, directive or pipe should usetree-shakeable providers - for example@Injectable({providedIn: 'root'}).

Unlike a realNgModule, a standalone component (and its "virtual"NgModule) can NOT specify providers to be instantiated on a top level injector.

Use-cases and code examples

This section goes over several practical use-cases and provides code examples for each use-case. Here we don't introduce any new concepts nor APIs but rather "derive" them from the fundamental design choices outlined so far.

@component /@directive /@pipe APIs

Standalone components, directives and pipes

A component, directive, or pipe can be marked as "standalone". This clearly signals that the “standalone” entity is not declared in any NgModule and thus is not part of anyNgModule.

Example component:

@Component({selector:'first-standalone-component',standalone:true,template:`I'm first!`})exportclassFirstStandaloneComponent{}

Example directive:

@Directive({selector:'[standaloneRedBorder]',standalone:true,host:{style:'border: 2px dashed red'}})exportclassStandaloneRedBorderDirective{}

Example pipe:

@Pipe({name:'standaloneStar',standalone:true})exportclassStandaloneStarPipeimplementsPipeTransform{transform(value){conststars=newArray(value.length);returnstars.fill('*').join('');}}

Notable points:

  • the same concept of "standalone" applies to components, directives and pipes;
  • the same syntax (standalone: true) is used to mark a component, directive or pipe as standalone.

Standalone components using custom elements

Standalone components can use custom elements in a template by specifying an appropriate element name validation schema, ex.:

import{Component,CUSTOM_ELEMENTS_SCHEMA}from'@angular/core';@Component({selector:'using-ce-component',standalone:true,template:`<custom-element></custom-element>`,schemas:[CUSTOM_ELEMENTS_SCHEMA]})exportclassUsingCustomElementsComponent{}

Standalone component with template dependencies

Standalone components are not declared in anyNgModule but still need a way of specifying their template dependencies. This is done with theimports property of the@Component decorator:

import{FirstStandaloneComponent}from'./firstStandalone.component';@Component({selector:'standalone-importing-standalone-component',standalone:true,imports:[FirstStandaloneComponent],template:`    Turtles all the way down:    <first-standalone-component></first-standalone-component>  `})exportclassStandaloneImportingStandaloneComponent{}

It is also possible to directly depend on components, directives and pipes exported by existingNgModules:

import{FormsModule}from'@angular/forms';@Component({selector:'standalone-with-import-component',standalone:true,imports:[FormsModule],template:`    Forms work: <input [(ngModel)]="name" /> (name = {{ name }})  `})exportclassStandaloneWithImportComponent{name='Daft Punk';}

The@Component.imports supports the same syntax and semantics as@NgModule.imports. In practice it means that users could group several collaborating directives in anArray and use such group in@Component.imports:

// imports const COLLABORATING_DIRECTIVES = [DirectiveFoo, DirectiveBar];import{COLLABORATING_DIRECTIVES}from'./collaborating-group';@Component({selector:'standalone-importing-standalone-component',standalone:true,imports:[COLLABORATING_DIRECTIVES],template:`<div [foo]="exp" [bar]="exp"></div>`})exportclassStandaloneImportingStandaloneComponent{}

This technique makes it possible to create groups of collaborating standalone components, directives and pipes (ones that should match together on a given element) without needing anNgModule.

Notable points:

  • standalone components can depend on existingNgModules (no changes are required to those modules) - this means that standalone components can take advantage of the entire existing ecosystem of libraries exposed asNgModules;
  • with the "virtualNgModule" mental model we can think of the imports property as "importNgModule"s here. There is really no distinction between importing an existingNgModule and a standalone component, directive or pipe - we always import anNgModule (a "real" or a "virtual" one);
  • theimports property has the same syntax and semantics as the same property on the@NgModule decorator. Most notably, the value of this property must be statically analyzable.

Libraries

With the introduction of standalone components, directives and pipes we open up a debate on changes to how libraries should be architected in response to this proposal. Essentially a library author will have the following choices:

  1. export anNgModule only;
  2. export both standalone components, directives, and pipes, as well as an "aggregating"NgModule (for applications that prefer to use them);
  3. export a set of standalone components, directives, and pipes only.

Regardless of the final recommendation and the exact choice done by the library author it is important to note that all the options listed above are possible.

To start with, library authors can continue to publish the existingNgModule without any changes. Those are guaranteed to work as-is. This is also the best choice for libraries composed of collaborating directives that must match on the same element (theNgModel +DefaultValueAccessor combination is a good example).

Then, a library might choose to expose standalone components, directives and pipes but still create aNgModule:

@Directive({selector:'[blueBorder]',standalone:true,host:{style:'border: 2px dashed blue'}})exportclassBlueBorderDirective{}@Pipe({name:'blackHole',standalone:true})exportclassBlackHolePipeimplementsPipeTransform{transform(value){return'';}}// backward-compatibility NgModule@NgModule({exports:[BlueBorderDirective,BlackHolePipe]})exportclassLibModule{}

Finally, a library author might choose to export exclusively a set of standalone components, directives and pipes. Such a set would be usable from standalone components and could be imported into anyNgModule (if an application chooses to use them). This is a good choice when a library consists of independent and non-cooperating components, directives and pipes.

Notable points:

  • libraries are free to choose how to export their deliverables, and should choose the approach that "makes most sense" given the expected usage patterns;
  • a library might choose to export standalone components, directives and pipes individually yet still provide an NgModule for applications that prefer to import a library as the "whole";

Other APIs using NgModule

The "standalone" concept makes it possible to simplify the existing APIs: generally speaking it should be possible to use a standalone component, directive or pipe in places where anNgModule was previously required.

This section contains examples of APIs that could be simplified. The intention is to show what might be possible rather than fully design or commit to those APIs.

Components: lazy loading and instantiation

At present, when lazy loading components in Angular, developers have to first lazy-load the component'sNgModule, and then use it to instantiate the component. TheNgModule context is required to ensure that declared components have their compilation scope and providers setup correctly.

With the "standalone" option we've got a guarantee that a component is "self-contained" and we can start using standalone components as a lazy-loading boundary:

@Component({selector:'app-component',template:'dynamically loaded: '})exportclassAppComponent{constructor(privatevcRef:ViewContainerRef){}ngOnInit(){import('./path/to/component').then(m=>{this.vcRef.createComponent(m.StandaloneComponent);});}}

Notable points:

  • theStandaloneComponent can be lazy-loaded without any associatedNgModule;
  • a lazy-loaded component can be instantiated in a view container as any other component.

Bootstrap

Angular developers need to create anNgModule in order to bootstrap even the simplest "Hello, World" application. In practice this means that theNgModule concept needs to be taught and learned while getting started with Angular.

With the "standalone" proposal implemented, we could introduce an alternative bootstrap API where a standalone component is directly used as a root component:

import{Component,bootstrapComponent}from'@angular/core';@Component({selector:'hello-world',standalone:true,template:'Hello, World!'})exportclassHelloWorldComponent{}bootstrapComponent(HelloWorldComponent);

Notable points:

  • applications using "standalone components" could be written without the need to learn about theNgModule concept and the associated APIs.

Router

Since a standalone component can be lazy loaded and dynamically instantiated we could modify router APIs to allow standalone, lazy-loaded leaf routes without the need for child route configuration or anNgModule:

RouterModule.forRoot([{path:'/some/route/to/standalone',loadComponent:()=>import('./standalone.cmp').then(m=>m.StandaloneRouteCmp)},{path:'/some/route/to/standalone/with/default/export',loadComponent:()=>import('./default-standalone.cmp')}]);

TestBed

While testing standalone components, we could avoid the need for a dedicated testingNgModule. In the simplest possible case a test could look like:

const fixture = TestBed.createStandaloneComponent(MyStandaloneComponent);

In case one needs to override components / directives in the component under test thecreateStandaloneComponent method could take an optional argument with overrides:

constfixture=TestBed.createStandaloneComponent(MyStandaloneComponent,{set:{imports:[ ...]}});

Possible API choices - soliciting community feedback

While brainstorming and designing the APIs presented here there were multiple times where we had hard time deciding between multiple, equally valid options. Here we would like present alternatives considered and solicit community feedback to choose the best option.

Syntax:imports vs. other names

The current proposal uses theimports keyword to specify dependencies of a standalone component. This name was chosen for the following reasons:

  • works well with the "virtualNgModule" mental model (existing@NgModule usesimports and we plan to have the exact same semantics);
  • JavaScript uses theimport keyword to denote the "bring something existing into a scope" operation and a standalone component brings an existing component, directive or pipe into its template compilation scope.

At the same time we hear the feedback where theimports word can be confused with the JavaScript imports and / or@NgModule.imports so we've also considered different names:

  • deps
  • uses

Auto-importing theCommonModule or its parts

One of the open questions is the explicitness of the dependency on theCommonModule. It should be noted that with the current proposal theCommonModule would have to be imported into the majority of non-trivial standalone components:

import{Component,Input}from'@angular/core';import{CommonModule}from'@angular/common';@Component({selector:'with-control-statments-component',standalone:true,imports:[CommonModule]template:`      <!-- I can use ngIf / ngFor since the CommonModule was imported -->      <ng-template [ngIf]="show">          <ul>              <li *ngFor="let item of items">                  {{item}}              </li>          </ul>      </ng-template>  `})exportclassWithControlStatmentsComponent{items=[...];        @Input()show=true}

While this is consistent with the mental model presented so far, there are alternative approaches.

The main trade off of the following variants is explicitness (at the cost of verbosity) and code succinctness (at the cost of introducing more "magic" to the system).

Our general preference is to lean towards explicitness and fine-granular dependencies with developer experience improved via tooling (compiler errors) and IDE auto-completion (via language service).

Auto-import theCommonModule

TheCommonModule could be an implicit import to all standalone components. In other, words all standalone components would behave as if they always imported theCommonModule:

import{Component,Input}from'@angular/core';@Component({selector:'with-sontrol-statments-component',standalone:true,template:`      <!-- I can use ngIf / ngFor without importing the CommonModule -->      <ng-template [ngIf]="show">          <ul>              <li *ngFor="let item of items">                  {{item}}              </li>          </ul>      </ng-template>  `})exportclassWithControlStatmentsComponent{items=[...];        @Input()show=true}

Here the component can use control flow (ngIf andngFor) and other items from theCommonModule (like theasync pipe) without an explicit import. This reduces verbosity / boilerplate code but makes Angular less explicit and "more magical".

Since we would like Angular to become more explicit and easier to reason about, this option is not preferred by the team.

Break theCommonModule into smaller modules

We could consider breaking theCommonModule into smaller ones (ex.ControlFlowModule,AsyncModule,I18nModule, ...) so users need only import parts that are actually used in a template. This would make template dependencies more fine-grained and explicit.

Additionally we could consider auto-importing a smaller module (ex. only auto-import theControlFlowModule containingNgIf,NgFor andNgSwitch).

Mark all the directives and pipes from theCommonModule asstandalone: true

To have even more fine-grained control over what is being visible to a template scope we could turn all the directives and pipes from theCommonModule into standalone ones. This would make it possible to import them individually in the@Component.imports (with the aid of IDE auto-completion powered by the Angular language service):

import{Component,Input}from'@angular/core';import{NgIf,NgFor}from'@angular/common';@Component({selector:'with-control-statments-component',standalone:true,imports:[NgIf,NgFor]template:`      <ng-template [ngIf]="show">          <ul>              <li *ngFor="let item of items">                  {{item}}              </li>          </ul>      </ng-template>  `})exportclassWithControlStatmentsComponent{items=[...];        @Input()show=true}

Again, we could decide to auto-import certain directives / pipes.

FAQ

The future

What will happen withNgModules in the future? Will these be deprecated / removed?

The short term-answer is thatNgModules are not going away and not getting deprecated - you can continue to write and consume existingNgModules.

The long-term answer is: we will monitor the adoption of standalone components, directives, and pipes, look into community feedback, and work on simplifying the overallNgModule story in a backward compatible way.

As of todayNgModules have many responsibilities, some of them being very useful (ex. grouping cooperating directives), some others having alternatives (ex. theproviders property vs. tree-shakable providers) and others being outright confusing. As the general direction we want to reduce or eliminate sources of complexity in theNgModule system by separating out some of its responsibilities into less tangled APIs that might eventually completely replaceNgModule.

We are very much aware thatNgModules play a very central role in Angular applications today, so we will move very carefully in this area.

Should I convert my apps / libraries to standalone components, directives and pipes?

We hope that the simplifications offered by this proposal will result in tangible benefits that will incentivise developer adoption. If you see benefits of using standalone components, directives and pipes — by all means please use them. IfNgModule works for you - continue to use them.

Initially we are planning on providing only minimal guidance on the "standalone" vs.NgModule-based approach (mostly around library publishing). We will continue to monitor the community's feedback and update guidance as we gather more data and learnings from real-life usage.

Syntax

Do we need thestandalone: true flag?

While it doesn't have to be thestandalone: true flag, we need some syntax to mark components, directives and pipes as "standalone". Reasons:

  • for humans:
    • clearly communicate the intention that "you can import me without an associatedNgModule and I will work correctly";
    • make it obvious what is the compilation scope of a given component (do I see all the matching components, directives, and pipes in imports or do I need to look into the associated NgModule to figure out the compilation scope?);
  • for tools:
    • speed up compilation (traverse import graph vs. scan the whole World);
    • produce a clear error message when a standalone component is misused / misconfigured; or when module-full component is imported directly (which breaks its encapsulation and results in the component behaving in unexpected/unpredictable ways).
  • for future evolution:
    • having a boolean flag could in the future (and based on the community feedback) be used to change the default fromstandalone: false tostandalone: true. Thus enabling safe, incremental, and automatable migration.

Also check thediscussion in the "Standalone components directives and pipes" section

Couldn’t we derive thestandalone flag from theimports presence?

We discussed this in detail, and the consensus was that the explicitness ofstandalone: true is desirable for a few reasons:

  • the "standalone" concept applies to components, directives and pipes whileimports only makes sense for components;
  • we can have standalone components without anyimports which shows thatstandalone andimports are 2 different concepts;
  • on the technical side, it makes the TypeScript typings more straightforward.

Performance

Does this proposal affect the ability to tree shake components, directives, pipes and providers?

There should be no change in what Angular compiler can tree-shake.

As of today the Angular compiler needs to understand what are the components, directives and pipes used in a component's template. The generated code hasonly references to what is being used in a template, regardless of how many components, directives or pipes are available in aNgModule. With the introduction of the standalone components, directives and pipes the generated code won't change —imports that are not used in a template will not be part of the generated code (and thus could be tree-shaken). The only difference withstandalone: true is that the compiler will have an easier time figuring out what is potentially used in a template — it can simply inspect theimports graph without going through the layer of indirection ofNgModules.

Tooling

What type of tooling can we expect if this proposal is implemented?

We want to have standalone components, directives and pipes well integrated into the existing Angular tooling. More specifically:

  • the language service could help by auto-importing standalone components, directives and pipes into@Component.imports or indicate unnecessaryimports based on what is being used in a template. Those are just examples but generally speaking we expect the language service to be fully integrated with the "standalone" way of doing things;
  • CLI should be able to scaffold standalone components, directives and pipes (a new option added tong new command);
  • schematics would be used for potential future migrations (ex. converting SCAM-pattern modules intostandalone: true, flipping the default value of thestandalone property etc.);

Documentation and learning resources

How do we teach this?

With the introduction of the standalone components, directives and pipes Angular developers will have a choice of structuring their applications aroundNgModules (as of today) or around standalone components (based on the proposal in this RFC). This choice will be reflected in the different learning journeys covered in our documentation:

  • a separate learning journey for people new to Angular, wanting to start on the "standalone" path;
  • for developers familiar with Angular andNgModules we will create a "moving to standalone components, directives and pipes" learning journey - there we will be able to compare and contrast the "standalone" approach with theNgModule-centered approach;
  • a separate documentation update will be needed for library authors in order to provide precise guidance for publishing libraries compatible with bothNgModule-based applications and "fully standalone" applications.

We will work on the exact documentation update plan as well as closely collaborate with Angular trainers / educators based on the RFC's feedback.

Additional resources

You must be logged in to vote

Replies: 70 comments 146 replies

Comment options

Thanks for the detailed explanation and examples. Looks good to me

Update (12/06/2021): Okay, after reading through comments, RFC (again), and based on my experience from other frameworks. Here are my thoughts

  1. I like the concept of Optional Modules approach at least for now and I agree this simplifies Angular mental model.
  2. And if you look at other frameworks like React, Vue, Svelte they all follow one simple architecture, component is basic building block of a web page and creating simple component is so easy and not tied any module or similar config. Angular took a first step in this direction by providing single file components (SFC). I have been using SFC in my projects and it simplifies the mental model of creating 4 files for every component. That's a good step in right direction
  3. When this feature is released I think it makes sense to default new components to Stand Alone components and make necessary changes in CLI. Eventually the modules concept can be deprecated and removed (through CLI flag). Other frameworks have shown that this model can exist and work well
  4. Based on interactions by Angular team on Twitter and Podcasts, it seems the feature is easy to implement. Perhaps an alpha version can be released with v14. I hope this feature to be well tested by GDE and community before releasing to all developers.
You must be logged in to vote
0 replies
Comment options

This proposal looks like a great way to remove a lot of boilerplate ngModule code.

How would lazy loading / bundling work if everything was heirachially declared through component imports? Would the compiler/optimiser make good choices?

You must be logged in to vote
5 replies
@HymanZHAN
Comment options

Yeah, I have a similar question. I remember about 2.5 years ago at Google I/O 19 (video), an example was demonstrated with pre-rendered static HTML and Angular code was loaded and injected progressively as the user interacts with the app. One key part of that demo was that code-splitting was happening at the component level to enable this progressive hydration. I haven't heard about it much since then though.

Would this optional NgModule and standalone component enable this use case in the future? Hopefully in a more or less automated manner? An initial bundle size of 10kb is really attractive.

@LanderBeeuwsaert
Comment options

Same question here. I'm also wondering about the lazy loading.
We have complex UI that show +-60 different components at the same time on the screen.
Would this then be 60 different files that have to be downloaded by the browser?
Wouldn't that be a problem, in the sense that there is a max limit of concurrent files that can be downloaded? (and probably each new file to be downloaded takes some overhead to start, just guessing here)

@robwormald
Comment options

there's nothing in this proposal that implies Components and files to be 1-1, as far as I can tell, nor that you'd necessarily have to use a dynamic import directly - meaning you could do more complex/intelligent things in terms of loading components as your application required:

{path:'/some/route/to/standalone',loadComponent:()=>someSmarterLoader('SomeComponent')

or

{path:'/some/route/to/standalone',loadComponent:()=>import('lazy-feature').then(m=>m.ComponentA)
//lazy-feature.tsexport{ComponentA,ComponentB}
@pkozlowski-opensource
Comment options

Yep,@robwormald it totally correct here - this proposal doesn't prescribe / implies single-file-components.

@pkozlowski-opensource
Comment options

Re-reading questions / discussions here a couple of more clarifications: standalone components / directives and pipes don't force any particular file / packaged bundles layout. While it will be possible to have more granular bundles (one bundle per component), nothing in this RFC is prescribing / forcing it. It means that it is equally valid and possible to have one standalone component per bundle as well the entire bundle with the entire app.

In short: with this RFC we will have more possibilities but nothing prescribes component-by-component bundling and loading.

Comment options

Could we have a setting in angular.json that sets/overrides the default standalone value?

You must be logged in to vote
2 replies
@jnizet
Comment options

I'd rather not have that: reading a component source code wouldn't be enough to know if the component is standalone or not. What would be nice OTOH is to be able to specify thatng generate must generate standalone components/pipes/directives by default, without having to set the standalone option.

@IgorMinar
Comment options

Thanks for the suggestion@penfold. We discussed this while creating the design and concluded that we can't have default overrides because then the components would behave differently based on this hidden (located far away) configuration.

What's worse, changing the config setting would completely change the semantics for many components (and potentially 3rd party components installed via npm). This would result in subtle runtime-only bugs that would cause your application to fall apart over time.

For these reasons, the only default is the one set in the framework, and as documented in the RFC, there is a clear path towards changing this default in a safe way if we decide that that's the right thing for the ecosystem.

Comment options

Tooling section could mention possible migration schematics. Maybe language service should be leveraged during the migration to collect correct list ofimports.

You must be logged in to vote
2 replies
@IgorMinar
Comment options

Good point,@minijus. I agree.@pkozlowski-opensource could we add that?

We've discussed this, and there is a clear path forward for building schematics that will automigrate SCAM components to standalone components. In most cases it should be a safe and fully automatable migration.

For non-SCAM components the migration is a bit trickier, but as you said, the tooling could figure out the imports and do other things, and then the developer could review and approve the migration component by component. So, yes!

@pkozlowski-opensource
Comment options

Mentioned potential use of schematics

Comment options

  1. Awesome feature
  2. Breaking CommonModule into smaller modules would be great.
  3. I thinkdeps oruses are better syntax thanimports for dependencies in order to grasp that is a different concept
  4. At least for a while, I would like to have thestandalone flag
  5. Again, awesome feature
You must be logged in to vote
3 replies
@IgorMinar
Comment options

At least for a while, I would like to have the standalone flag

@JoelNietoTec can you please clarify what you mean by this? thank you

@BrunoAMSilva
Comment options

I think he means that while ngModules aren't deprecated he would prefer to be explicit and have the flag. I would agree and that seems to be what's implied in the doc.

@alcfeoh
Comment options

I'd say +1 for not usingimports because I already anticipate all of my Angular learners asking: "Why does theimports inNgModule is only about modules while this one can have components and directives in it? Why aren't those indeclarations instead?".
Also, a common beginner question is: "Why do we import things twice?" (referring to the Typescript import + theimport inNgModule decorator).
So for the sake of illustrating that it's a different feature related to the "virtual ngModule" idea, using a different name would be very helpful.

Comment options

This is a great proposal and a great step in the right direction.

  1. I'd personally +1 either:
  • Auto-importCommonModule to Standalone Components
  • Auto-import certain directives and pipes to Standalone Components namely:ngIf,ngForOf,async.
  1. The imports on a standalone component could get unwieldy.
    Consider you're using Angular Material in your component:
@Component({imports:[CommonModule,FormsModule,MatButtonModule,MatIconModule,MatToolbarModule,MatMenuModule,],standalone:true,selector:'my-comp',templateUrl:'./template.html',stylesUrl:['./styles.css'],})

Being able to import these from a constant in a separate file is good, however, it still means you're looking elsewhere for the dependencies, albeit more explicitly.

  1. Understandably, thestandalone aids the development of tooling etc, but I'm not sure it should exist long term, or at least it should be v.quickly moved to betrue by default.

  2. Providers can be tree-shakeable using theprovidedIn property.
    Could we utilise this method for Components?

If a component states that it isprovidedIn: 'root' then it becomes available to any Component that needs it:

@Component({providedIn:'root',selector:'hello-world',template:'<h1>Hello World</h1>'styles:['h1 { color: red; }']})exportclassHelloWorldComponent{}@Component({selector:'app',template:'<hello-world></hello-world>'})exportclassAppComponent{}bootstrapComponent(AppComponent);

And if you wanted to include the component in a particular NgModule, you simply replace theprovidedIn: 'root' withprovidedIn: SharedModule
There is of course the question of does theHelloWorldComponent need to be imported somewhere for the TS compilation to find it and allow it to be registered.

  1. +1 foruses rather thanimports. In fact, I think that should be expanded toNgModule also. It should include another property,uses which is specific to Standalone Components. I think the concept ofimports: [*Component] will be a tough mental shift for a lot of current Angular Developers. Especially if we see:
// difficult to reason aboutdeclarations:[AComponent],imports:[CommonModule,FormsModule,BComponent,MatButtonModule]// easier to reason about and helps separate out that BComponent is self-contained// whereas AComponent needs the Modules in the importsdeclarations:[AComponent],uses:[BComponent],imports:[CommonModule,FormsModule,MatButtonModule]
  1. Overall I think this is great, and I look forward to a day where Angular's building blocks truly are single units of Components, Pipes and Directives!
You must be logged in to vote
0 replies
Comment options

Great proposal 🚀.

+1 for Auto-import the CommonModule.

I think we can accept a little bit of magic if it means we won't have to writeimports: [CommonModule, ...]
in every single component that we write.

  • Just like React removed the need to "import React" in every component;

-1 forimports: [ NgIf, NgFor ] because this will increase verbosity a lot.

Edit: How will this affect @angular/elements?

Edit2: How will this affect routing?
For ex.RouterModule.forChild(routes) andRouterModule.forRoot(routes),
What will happen with the DI part if we useRouterModule.forRoot(routes) in more than 1 standalone component?

You must be logged in to vote
2 replies
@mauromattos00
Comment options

I agree 100%! Auto importingCommonModule would really useful!

@IgorMinar
Comment options

Thanks for the feedback. The@angular/elements story will be impacted by this proposal as well, in a good way. Converting components to custom elements will become much more straightforward. We explored this a bit, but didn't have enough time to sort out all the details so we decided not to mention it in the RFC so that we are not confusing anyone.

@pkozlowski-opensource it might be worth mentioning in the RFC that @angular/elements would be positively impacted by this proposal, without going into details.

Comment options

Great stuff!
In my opinion explicitely specifying imports ist better than some magic.
If thestandalone flag is set upon codegeneration via the CLI it could just add the Commons Module to the imports, just as its done withViewEncapsulation orChangeDetectionStrategy. Specifying individual directives or pipes out ofCommonModule as imports/deps/uses would be great, but it should still be possible to use theCommonModule instead for convenience.

I would stick to the name imports. deps would be the only abreviation in the decorator.uses would be fine as well, no big preference here

You must be logged in to vote
1 reply
@MaxKless
Comment options

I agree on explicitly specifying imports.
If the angular compiler is able to tree shake the unused parts from theCommonModule anyway, it would be more convenient to import the whole thing and not worry about performance as opposed to having to import seperateControlFlowModule,AsyncModule etc.

+1 on imports, it's instantly clear what it does and fits with theNgModule syntax.

Comment options

I think this is a phenomenal proposal and am most excited for the ability to easily lazy load components at runtime and have them be split into their own bundle!

My preference with regards to the howCommonModule will relate tostandalone: true would be to leave it up to the developer - though a flag inangular.json may be nice to have to determine whether it should be automatically imported or not.

In the long run, I think splitting things likeNgIf,NgFor, and theAsyncPipe into standalone directives and pipes would be a welcome change.

Thanks for all of your hard work on this proposal!

You must be logged in to vote
1 reply
@IgorMinar
Comment options

My preference with regards to the how CommonModule will relate to standalone: true would be to leave it up to the developer - though a flag in angular.json may be nice to have to determine whether it should be automatically imported or not.

Thanks for the note, Andy. I addressed this in an earlier comment. In short - this wouldn't work:#43784 (comment) - let's keep the discussion on that topic in that thread.

Comment options

Great proposal! I am really looking forward to this.

Some thoughts, questions, and feedback:

  1. Some auto-importing ofCommonModule provides great convenience. Like in Vue, you don't need to import anything to usev-if andv-for. If the Angular compiler is able to tree shake unused directives anyways, I'd go for auto-importing at least for a subset ofCommonModule.

  2. DividingCommonModule into smaller pieces is also great. Can be helpful to increase the explicitness.

  3. How would this affect the code-splitting strategies and lazy-loading performance? I surely believe it can affect it in a positive way 😄, but would it be possible to achieve 100% component lazy-loading in a more or less automated manner (without manually callingimport everywhere)?

    As I've commented under another reply, I remember seeingthe Angular team demoing this application at Google I/O 19, which was pre-rendered + progressively hydrated, with an impressive initial payload of 10kb (Now that I think more about it, it much resembles Misko's new work on Qwik). I understand that this is not being emphasized so it's probably a non-goal and I don't know how closely related these two features are, but I'd love to hear what the Angular team thinks of it as an initial payload of only 10kb is just too good to ignore. Also, various Ivy talks in various conferences mentioned its ability so I believe in Ivy's potential.

  4. How will it affect SSR/SSG?

  5. Not much preference onimports vsdeps/uses, but I personally likeimports better.

  6. Astandalone option inangular.json would be great.

A big thank you for everyone's effort! I can't wait to see this sorted out and come to reality! 💖

You must be logged in to vote
3 replies
@HymanZHAN
Comment options

One thing that came to mind (even though I know this is not gonna happen anytime soon), if we were to deprecate NgModule in the future, would we still have this opinionated style guide? Like if it's all just components, some architecture guidelines like "feature modules" will not exist.
I like the opinionated way Angular helps to structure applications, so much so that I am replicating it in a Vue project that I work on at work. I hope we don't lose this trait of Angular.

@LayZeeDK
Comment options

In Nx workspaces, we use a workspace library as an explicit architectural boundary.

Depending on your definition, this proposal doesn't remove the need for feature modules because they often orchestrate routes and feature module injectors.

@HymanZHAN
Comment options

Yeah, butNgModule is the current and recommended way to explicitly orchestrate and group things for projects that are not monorepos. If it got remove (in some distant future), wouldn't it now be the developers' burden to come up with their own solution (with the monorepo style + libs as an option)? I can somewhat foresee people trying to reinvent their ownNgModules, just like how quite a few people have done in Vue and React projects. I just don't want this "opinionated" trait lost and have a dozen "community" best practices for creating your ownNgModules in the future.

Comment options

Thanks a lot for this great RFC!

Some remarks:

  1. standalone makes sense. The arguments why we need this are clear for me.
  2. "Magical auto imports" are OK, but then please provide an opt-out flag on component level.
    • Would it be possible to add a build time config (developer can add an array of auto imports) for the "magical auto imports" and a switch to opt-out of them on component level?
  3. How does the concrete DI tree will look like for lazy components? For lazy modules, we have an additional module level DI node as child of the root module node. Will a lazy component be added to the component DI tree like any other eager component?
  4. imports vs.deps: both have the mentioned advantages and disadvantages.deps is used for DI factory functions, with a different meaning, as well. Nevertheless,deps could be easier for beginners (because of no confusion with JS imports) and we do not necessarily need to find a matching syntax with NgModules as this concept would make them optional. I would vote fordeps.
You must be logged in to vote
0 replies
Comment options

One capability that seems to be lost with standalone components, unless I'm missing something, is encapsulation, i.e. the ability to make some components only usable within a specific feature module/component.

As far as I understand, as soon as a component is standalone, every other component/module of the application is able to import and use it, even though it has been designed to be used only as a part of another component or feature module.

This is possible, and IMHO, quite useful, with modules, which can declare components and not export them, to keep them private, and thus easily modifiable/deletable with an impact limited to their declaring module.

It would maybe be a good idea to be able to specify the authorized scope of a component, i.e. mark it as usable only in the template of some other components, or importable by only some other modules.

You must be logged in to vote
3 replies
@LayZeeDK
Comment options

In Nx workspaces, we use a workspace library as an explicit architectural boundary. Each one having an explicitly defined public API with tooling to prevent deep or forbidden imports.

@pkozlowski-opensource
Comment options

@jnizet I hear you and you are right. At the same time it moves us closer to the JS ecosystem where you've got exactly the same problem of the "package private" members. As@LayZeeDK hinted I also believe that the proper solution is to have "package" / "module" internal exports but don't expose those as part of the "public API".

@alxhub
Comment options

alxhubOct 12, 2021
Collaborator

This is indeed a downside of usingstandalone if you intend to carefully manage its visibility to the rest of your applications. There are two cases here:

  • A component that's used internally within a library/package, and shouldn't be visible externally.

The answer here is straightforward - hiding the component by not exporting it from the library entrypoint.

  • A component/directive that's exposed via anNgModule but shouldn't be used directly. For example, maybe the directive is an implementation detail of the system in question (such asDefaultControlValueAccessor inFormsModule).

This is a little trickier, because such components/directives still need to be exported from the entrypoint (since they will be consumed in templates, albeit indirectly). One would have to use a similar trick as Angular, to export the component with a "private" name.

Comment options

Maybe it would be nice to be able to opt-in to auto-importingCommonModule. Some teams might be okay with the magic, some teams might prefer being explicit. Perhaps an application could define its own set of globaluses/imports/deps? I guess that could potentially be a bit of a footgun.

You must be logged in to vote
2 replies
@mlc-mlapis
Comment options

Let's keep it predictable and readable on the first try (without deep studying what combinations are in the play) as much as possible. It's one of the most valuable advantages of the Angular framework at all.

@MaxKless
Comment options

I've seen a similar proposal in another thread but I feel like it doesn't add much in terms of readability and makes the code more confusing. If you need a set of imports that are shared between many or all components, it seems smarter to create a static array and import it as they show in the RFC.

Comment options

Thoughts:

  • overall pretty awesome.
  • personally, i'd prefer a new@NgComponent decorator for this use case, vs overloading the existing@Component decorator, which would eliminate the need forstandalone: true or a bunch of flag flipping. Ditto for@NgPipe/@NgDirective/@NgEtc Probably controversial, but putting it out there.
  • I'm against including theCommonModule automatically, not least of which because that introduces a dependency on@angular/common which has to be eliminated through tooling.

clearly communicate the intention that "you can import me without an associated NgModule and I will work correctly";

I wonder about this, since this assumes you have access to the source code, which is the case for code you own but not dependencies.

One thought might be mirroring theprovidedIn API for "standalone" components. An@NgComponent/NgDirective() could optionally add adeclaredIn: SomeModule property to associate with a set of related directives / pipes / providers?

You must be logged in to vote
7 replies
@stefan-schweiger
Comment options

I agree about the first part, but I think at least a basic set of ghings likengIf,ngFor, etc. should at least for components always be available. It probably doesn't need to be the full CommonModule though.

@pkozlowski-opensource
Comment options

@robwormald thnx for taking time to read / comment. Couple of comments:

@NgComponent / @NgPipe / @NgDirective instead ofstandalone: true - not too controversial - we had a similar idea and TBH I like it quite a bit. In the end decided to go withstandalone: true mostly because it would be easier to "flip the default" if it comes to this (in the very distant future). But this is still on the table;

I wonder about this, since this assumes you have access to the source code, which is the case for code you own but not dependencies.

Not sure I got what you mean, we must be crossing the wires somewhere. Needs more discussion / elaboration.

@JoepKockelkorn
Comment options

@pkozlowski-opensource I think what Rob means is that a library author can expose whatever he wants to expose in the docs of a library, which could mean thestandalone value is not available, thus it will probably not "clearly communicate the intention" always.

@pkozlowski-opensource
Comment options

Right. The assumption here is thatstandalone: true / false becomes part of the@Component /@Directive metadata (on the same level as a selector, a set of@Input /@Output etc.). Obviously lib authors could omit this part in their docs but this would mean that important information is not given to the users.

@wardbell
Comment options

I like@NgComponent / @NgPipe / @NgDirective and not just because it eliminates the need forstandalone. See why in comments I made belowL#43784 (comment)

Comment options

Awesome feature, this will make applications much simpler. No more one module per component in reusable components :)
Also, I've been using the SCAM pattern without knowing its existence 😅

About the auto import of CommonModule, I'd prefer it to be explicitly set or, as other already mentioned, an opt-in config in the angular.json

You must be logged in to vote
0 replies
Comment options

Lazy Side Effects

In order to implement feature likesngrx'sStoreModule.forFeature() andEffectsModule.forFeature(), we should be able trigger side effects that survive the declarable's lifecycle.

In the following example, (if everything is eager loaded),sayHello is added to the root injector's providers, andHello! is displayedonce even ifHelloComponent is instantiated multiple times.

constSIDE_EFFECT=newInjectionToken('side effect');@NgModule()exportclassSideEffectModule{constructor(@Inject(SIDE_EFFECT)fns){for(constfnoffns){fn();}}staticforFeature({ sideEffect}):ModuleWithProviders<SideEffectModule>{return{ngModule:SideEffectModule,providers:[{provide:SIDE_EFFECT,useValue:sideEffect,multi:true,},],};}}@Component({standalone:true,imports:[SideEffectModule.forFeature({sideEffect:sayHello})],  ...})exportclassHelloComponent{}functionsayHello(){console.log('Hello!')}

How can we achieve the same result without modules and without changing anything inHelloComponent's class implementation(e.g. injecting aprovidedIn: root service)? or maybe in other words, how could we declare providers that roll up to the root injector?

You must be logged in to vote
7 replies
@mikezks
Comment options

Valid point that you address related to the store.
This could easily be done with a generic function that is used within the standalone Component providers. It would basically use a DI factory function an register the necessary config that is normally registered through the NgModule forFeature import.

Edit: read your commonent after I replied - so, I agree, you already described that. 😉

@pkozlowski-opensource
Comment options

Yep, this is a valid use-case and I've gotsome ideas on how to tackle it elegantly in the DI config (roughly similar toAPP_INITIALIZER but executed when a given DI context and up in a newInjector). Rough ideas for now, we are discussing it internally - stay tuned. But yeh, the general thinking is that it should be totally possible without aNgModule

@mikezks
Comment options

I think it is important to have the routing and redux features available as the standalone Component feature gets available.

@pkozlowski-opensource
Comment options

Yes, I agree. Not strictly part of this RFC but I believe that having ideas of the module-less APIs for those use-cases is important.

@mikezks
Comment options

@pkozlowski-opensource, great to hear that - thanks a lot for your work.

DI functions are really powerful for such use cases. We can even merge different 'multi' definitions across multiple DI nodes, which is quite nice for some implementations.

Comment options

Hello! First of all, this idea is amazing! Something that I want to know is if you are planning to support a "ComponentWithProviders" feature to configure standalone components.

You must be logged in to vote
2 replies
@rothsandro
Comment options

Providers on components are already supported. Or do you mean something different?

@AsmisAlan
Comment options

Sorry, I forgot to add theModuleWithProviders link. And yes, I was talking about having something like this:

@Component({standalone:true,///.....})classDateTimeFormComponent{staticconfigure(localeId:string) :ComponentWithProviders{return{component:DateTimeFormComponent,providers:[{provider:LOCALE_ID,useValue:localeId}]}}constructor(@Inject(LOCALE_ID)localeId){}}@Component({standalone:true,///.....imports:[DateTimeFormComponent.configure('fr')]})classMyParentComponent{}
Comment options

  1. This is a really cool feature that personally I've been waiting for years. Kudos!
  2. IMHO All the defaults suggested by the team make most sense (imports notdeps;standalone flag etc).
  3. I truly hope the suggested changes are just the first step to making component, directives and pipes' dependencies more explicit and easily overridable, similar to how we handle service dependencies. Please see this feature request for a verbose explanation of what I mean and the suggested syntax:Explicit component/directive/pipe dependencies (getting rid of the need for NgModules for most cases) #35646
You must be logged in to vote
1 reply
@pkozlowski-opensource
Comment options

Thnx for the reference@Maximaximum - I've linked this RFC from the mentioned issue.

Comment options

This is a really cool and useful feature. Thanks and Kudos!
As someone mentioned above, a new decorator (as opposed to overloading the existing@Component decorator) is something I'd vote for.

You must be logged in to vote
0 replies
Comment options

This is a very interesting and well-documented proposal, congrats and many thanks to the writer 👏 👏 👏

I believe it goes in the good direction, some of the most confusing parts of Angular are related to NgModule (lazy loading, transitive dependencies, routing issues, DI, etc.). It would improve developer experience, reuse and sharing specially for simple GUI componentes, and one big point to me is that it could make testing easier and with less bolierplate.

Regarding the feedback solicited:

  • I would prefer using import keyword rather than introducing a new keyword in the framework, it goes well with the existing mental model. There is some old confusion with JS imports/exports and Angular imports/exports but we have learned to live with it hehe
  • I would prefer having to import CommonModule in each standalone component rather than autoimport, it is just a bit more verbose (+1 line) but more explicit and follows the same behaviour as now. I would not split the CommonModule in parts, it would make it more confusing and less straight-forwards to use.

My two cents ✌️

You must be logged in to vote
6 replies
@magicmatatjahu
Comment options

@bbarry The problem with lazy imports in imports is that they are asynchronous (they create Promise) and NgModules themselves do not support asynchronous ModuleWithProviders/NgModules classes and here is the problem: how and when to resolve this Promise if the DOM rendering is done synchronously?

@bbarry
Comment options

It isn't without problems... the ones you describe are very likely solvable but would bring with them increased complexity and size of the framework.

This wouldn't be about actually async loading most of the time; rather the value I think would be in identifying ideal splits for webpack. If there was a way for angular identify template content required for FCP vs not, something like this could be used to give angular the ability to further optimize the initial package size.

@SanderElias
Comment options

@bbarry With ESM modules, top-level await is a thing. While the async support in angular poses a lot of extra challenges, this might be an option that can be considdered. (the wait will be runbefore angular starts its initialization.

@pkozlowski-opensource, this might be something that needs some attention too. As I'm not 100% sure how it would pan out ;)

import{Component}from'@angular/core';constlazyModule=awaitimport('./template-deps')const{ FooComponent, BarDirective, BazPipe}=lazyModule@Component({standalone:true,imports:[FooComponent,BarDirective,BazPipe],template:`    <foo-cmp></foo-cmp>    <div bar>{{expr | baz}}</div>  `})exportclassExampleStandaloneComponent{}

While the code for that seems reasonable, I'm not entirely sure this is a good idea.

@vecernik
Comment options

Can foo-cmp component and bar directive be detected by compiler automatically without that boilerplate code?

@bbarry
Comment options

@SanderElias I think that might almost just simply work today? Babel/Webpack will convert that into something vaguely like:

import{Component}from'@angular/core';awaitimport('./template-deps').then(({ FooComponent, BarDirective, BazPipe})=>{    @Component({imports:[FooComponent,BarDirective,BazPipe],template:`      <foo-cmp></foo-cmp>      <div bar>{{expr | baz}}</div>    `})classExampleStandaloneComponent{}    @Module(...)classExampleStandaloneHiddenModule{}return{ ExampleStandaloneComponent, ExampleStandaloneHiddenModule};});

the difficulty is that it is aPromise<ExampleStandaloneHiddenModule> now and so you cannot put it in the imports list without an await there (and so on all the way out to the bootstrapper). Your main.ts winds up being:

import{enableProdMode}from'@angular/core';import{platformBrowserDynamic}from'@angular/platform-browser-dynamic';import{environment}from'./environments/environment';const{ AppModule}=awaitimport('./app/app.module');if(environment.production){enableProdMode();}platformBrowserDynamic().bootstrapModule(AppModule).catch((err)=>console.error(err));

(I still don't know if this is a "good idea.")

You would need a custom webpack configuration to enable top level awaits (custom-webpack fromhttps://github.com/just-jeb/angular-builders can probably do it).


@vecernik there needs to be some way for the angular template compiler to know which file contains the component it should load for the syntax<foo-cmp>.

Comment options

While we think about naming of things and how all of this will be documented, will the group ofcomponents, directives, and pipes still be referred to asdeclarables? Can they still be called declarables if they may not be declared in NgModule anymore?

@Component{selector: ...,standalone:true,uses:[declarables],  ...}exportclass ...
You must be logged in to vote
3 replies
@pkozlowski-opensource
Comment options

No, I don't think we would callall components / directives / pipes "declarables". Standalone components / directives / pipes would have to be excluded from the "declarables" set.

@samherrmann
Comment options

Yeah I agree that it would be wrong to call them declarables. I'm trying to think if there's another good name for that group so that in documentation and speech we don't have to keep repeating "components, directives, and pipes". Unfortunately I don't have a good suggestion at the moment. The meaning of the name should convey "the things we use in templates".

@twopelu
Comment options

Couldn't standalone components be considered as auto-declared (even they are not explicitly declared in a module), and so keep naming them as declarables? Just to keep things simple...

Comment options

As someone who's been using SCAMs for some time now, standalone components make a lot of sense and will be awesome to have. As for auto-importingCommonModule, I would prefer not to because I like the explicitness of importing it when there's a need for it. I work with quite a lot of small components whereCommonModule is not needed.

You must be logged in to vote
1 reply
@miladvafaeifard
Comment options

just an idea that possible to disable (enable by default) the following example:

@Component{selector: ...,imports:[CommonModule.disable(),ngFor,    ....],standalone:true,  ...}exportclass ...
Comment options

An idea to give the option to addCommonModule somewhat "automatically" as a dependency could be to have the standalone flag support a set of constants instead of a boolean (just like it's done for change detection strategy):

@Component{  selector: ...,  standalone: Standalone.WithCommonModule,  ...}export class ...
You must be logged in to vote
0 replies
Comment options

I'm looking forward to seeing this proposal implemented in my projects 👍

Here's my solicited feedback:

  • useimport keyword to specify dependencies
  • explicit import of CommonModule is fine. I can't foresee I'd ever have a need for the smaller CommonModule parts, but it wouldn't impact me either if those existed in addition of the CommonModule
You must be logged in to vote
3 replies
@alcfeoh
Comment options

What if you're creating a standalone pipe or directive? Most likely it won't needngFor,ngIf, etc. to get imported.

@PowerKiKi
Comment options

If I don't need anything from CommonModule, then I wouldn't import it at all. And if I need only one thing from CommonModule then I'd import it all for the convenience of not having to remember which directives is part of which module. And all of that with only a very minimal impact on compilation time, and zero impact on runtime. That seems like an acceptable balance between convenience and performance.

@alcfeoh
Comment options

You're right, I misread your comment. I thought you advocated for auto-import ofCommonModule all the time, but that's not the case.

Comment options

how about

@standalone()
@component({
...same thing here...
})

You must be logged in to vote
0 replies
Comment options

I forgot to say: personally I would prefer explicit imports forCommonModule or its members. Ideally, both the individual member imports (likengIfDirective) as well as wholeCommonModule imports should be equally available.

Can't wait until theStandalone components, directives and pipes feature is released! :)

You must be logged in to vote
0 replies
Comment options

This looks awesome. This can't happen quickly enough. Double thumbs up from me.

You must be logged in to vote
0 replies
Comment options

Nice article. What if we forget about NgModule al together and let the compiler detect components and directives from its template and locate and use them from sources? It would greatly simplify the Angular mental model and ease the pain for newcomers.

You must be logged in to vote
4 replies
@LayZeeDK
Comment options

It's been discussed by the Angular team to make the Angular Language Server smarter so that it suggests component imports based on its template.

@vecernik
Comment options

Is Language Server also used for dev/prod builds?

@LayZeeDK
Comment options

No, this would probably be something like fixers in your editor.

@Maximaximum
Comment options

@vecernik It's crucial to let the developer decide which directives/components/pipes to use in each specific case. You might have multiple components that have the same selector, and it's extremely important to let YOU decide which specific directive/component/pipe to use. Therefore, intellisense should (ideally) be able to make suggestions for you, but you should declare the dependencies explicitly.

Comment options

Are there any chances that this will be included into the Angular v14 release?

You must be logged in to vote
0 replies
Comment options

tl;dr; thank you for all the feedback - our intention is to proceed with the design described in this RFC.

First of all we would like to thank everyone who commented on, or otherwise engaged in the discussion on this RFC. We had over 140 comments from more than 60 people on this RFC aloneplus additional conversations through other channels (social media, meetups, conferences). We were blown away by the quality and depth of the discussion. Thank you!

Based on all the comments and feedback we believe that the design was well understood and received. Our interpretation is that the Angular community supports our intention of moving the framework in the proposed direction. And critically, we haven't seen any use-cases or technical constraints that would "break" the design.

We've solicited feedback for some specific design questions in this RFC and your input was very valuable. Incorporating this feedback in our design, we intend to:

  • require importingCommonModule, and not have it be implicitly imported in all components
  • allow individual directives/pipes withinCommonModule to be imported independently
  • proceed with theimports name for the property in@Component. While the feedback shows a variety of preferences, we feel that none of the proposed alternatives capture the full meaning of this field.

In your feedback, you raised several important questions that need additional design consideration:

  • best practices for lazy-loading application parts withoutNgModule (with and without@angular/router);
  • bootstrapping an application without requiring anNgModule;
  • ensuring that the list ofimports in@Component remains manageable, either via tooling or grouping APIs;
  • ensuring that Angular provides guidance, documentation and examples for applications opting into the "standalone" world.

Based on your feedback, we are confident enough in the core design of standalone components to proceed with implementation, but we will also embark on designing additional APIs and functionality when needed.

You must be logged in to vote
0 replies
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Category
RFCs
Labels
None yet
68 participants
@pkozlowski-opensource@PowerKiKi@bbarry@xorets@wardbell@IgorMinar@EmpeRoar@penfold@e-oz@robwormald@cyrilletuzi@flensrocker@amakhrov@phenomnomnominal@dzhavat@LanderBeeuwsaert@vecernik@SanderElias@titusfx@benlesh@jnizetand others

[8]ページ先頭

©2009-2025 Movatter.jp