Replies: 36 comments 155 replies
-
2 - IMHO NgModules are most representative of "applications" today so In order of preference:
3a - I don't see an option to pass a parent injector to any of these APIs? Plenty of use cases where I'd want to have some kind of "headless" root injector with services I'd want to share across various components. Presumably the 3b - I also don't see an ability to pass a specific DOM node as the render target, which is required for dynamic use cases (vs the "global" query-the-document for one root element style) 4 - nope, and I think it muddies the waters. I realize the teams definition of "standalone component" probably refers to "standalone from an NgModule" but imho the actual mental model is "standalone from an application" IOW, unless you're planning to completely deprecate NgModules, it's better to stick with the concept of "NgModules-as-applications" and stick to the term "Component" (and don't use AppComponent in the examples 😉) for standalone stuff. |
Beta Was this translation helpful? Give feedback.
-
This was a great RFC 🚀 Questions:
Answers:
|
Beta Was this translation helpful? Give feedback.
-
Component-level initializerHow about an initializer tied to the injector scope of a standalone component as suggested by Younes (@yjaaidi)? @Component({
standalone: true,
providers: [
{
provide: INJECTOR_INITIALIZER,
multi: true,
useValue: () => inject(LoadingService).markLoaded()
}
],
template: '',
})
export class MyStandaloneComponent {} |
Beta Was this translation helpful? Give feedback.
-
Route support for default exportsYes, please. In addition to a named export similar to the current Router API. // app.component.ts
import { Component } from '@angular/core';
import { configureRouter, RouterModule, Routes, withRoutes } from '@angular/router';
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin.routes'), // 👈
},
];
@Component({
standalone: true,
imports: [
RouterModule,
],
providers: [
configureRouter({
anchorScrolling: 'enabled',
initialNavigation: 'enabledNonBlocking',
scrollPositionRestoration: 'enabled',
}),
withRoutes(routes),
],
template: '<router-outlet></router-outlet>'
})
export class AppComponent {} // admin.routes.ts
// 👇
export default const routes: Routes = [
// or:
// export default const [
{ path: 'users', component: AdminUsersComponent },
{ path: 'teams', component: AdminTeamsComponent },
]; versus // app.component.ts
import { Component } from '@angular/core';
import { configureRouter, RouterModule, Routes, withRoutes } from '@angular/router';
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin.routes').then(esModule => esModule.routes), // 👈
},
];
@Component({
standalone: true,
imports: [
RouterModule,
],
providers: [
configureRouter({
anchorScrolling: 'enabled',
initialNavigation: 'enabledNonBlocking',
scrollPositionRestoration: 'enabled',
}),
withRoutes(routes),
],
template: '<router-outlet></router-outlet>'
})
export class AppComponent {} // admin.routes.ts
export const routes: Routes = [ // 👈
{ path: 'users', component: AdminUsersComponent },
{ path: 'teams', component: AdminTeamsComponent },
]; |
Beta Was this translation helpful? Give feedback.
-
Follow ups:
To illustrate the basic idea: @Component({
template: 'Hello {{name}}!'
})
export class HelloWorld {
@Input() name = 'World'
}
const host = document.getElementById('root');
const compRef = bootstrapComponent(HelloWorld, {host});
//a few moments later
compRef.instance.name = 'Angular';
//compRef.detectChanges() ?
//detectChanges(compRef) ? |
Beta Was this translation helpful? Give feedback.
-
How does the new standalone apis play with ngExpressEngine ? |
Beta Was this translation helpful? Give feedback.
-
Lazy loading components requires a "loader module" next to the component. |
Beta Was this translation helpful? Give feedback.
-
I'm curious too., have a good work. @BioPhoton |
Beta Was this translation helpful? Give feedback.
-
TestBed APIPlease consider feedback on the extended CommonModule default import
Given this component // my-standalone.component.ts
@Component({
standalone: true,
template: '{{ text$ | async }}',
})
export class MyStandaloneComponent {} This component would fail at compile time (AOT) or runtime (JIT) because Your suggestion in the Standalone Components RFCconst fixture = TestBed.createStandaloneComponent(MyStandaloneComponent); or with metadata overrides that must support const fixture = TestBed.createStandaloneComponent(MyStandaloneComponent, {
set: {
imports: [
ChildComponentStub,
CommonModuleStub,
],
},
}); Younes' suggestion in the Standalone Components RFCSuggestion by Younes (@yjaaidi): const fixture = TestBed.createComponent(MyStandaloneComponent); My suggestions in the Standalone Components RFCSuggestions by me TestBed.overrideComponent(MyStandaloneComponent, {
set: {
imports: [
ChildComponentStub,
CommonModuleStub,
],
},
});
const fixture = TestBed.createComponent(MyStandaloneComponent); Legacy component with stubbed provider TestBed.configureTestingModule({
declarations: [UserComponent],
providers: [{ provide: UserService, useClass: UserServiceStub }],
});
const fixture = TestBed.createComponent(UserComponent); Standalone component with provider stubbed in testing module TestBed.configureTestingModule({
providers: [{ provide: UserService, useClass: UserServiceStub }],
});
const fixture = TestBed.createComponent(UserComponent); Standalone component with provider stubbed in metadata overrides TestBed.overrideComponent(MyStandaloneComponent, {
add: {
providers: [
{ provide: UserService, useClass: UserServiceStub }
]
},
});
const fixture = TestBed.createComponent(UserComponent); Legacy component with inline template and styles TestBed.configureTestingModule({
declarations: [UserComponent],
});
const fixture = TestBed.createComponent(UserComponent); Standalone component with inline template and styles const fixture = TestBed.createComponent(UserComponent); Legacy component with external template and styles TestBed.configureTestingModule({
declarations: [UserComponent],
});
const fixture = TestBed.createComponent(UserComponent); Standalone component with external template and styles const fixture = TestBed.createComponent(UserComponent); Alternative suggestion by me in the Standalone Components RFCTestBed.configureTestingModule({
imports: [UserComponent],
providers: [{ provide: UserService, useClass: UserServiceStub }],
});
const fixture = TestBed.createComponent(UserComponent); |
Beta Was this translation helpful? Give feedback.
-
Will NgZone be supported?Will |
Beta Was this translation helpful? Give feedback.
-
bootstrapComponent optionsA. Will |
Beta Was this translation helpful? Give feedback.
-
Which platform does bootstrapComponent use?A. Which package will host |
Beta Was this translation helpful? Give feedback.
-
Great RFC! Answers to Questions: 2. The naming of
|
Beta Was this translation helpful? Give feedback.
-
What about Angular Elements? Currently it's needed to create a module and bootstrap the main component on But it isn't a great opportunity to also simplify that process? |
Beta Was this translation helpful? Give feedback.
-
I don't know of any.
I Agree with @robwormald here.
However, I disagree with Rob about the DOM node, which should be taken care of by the selector in the component. I don't see a use-case where one would need to boot the same 'top' component multiple times in a document. I don't see this as an extension/replacement for Angular Elements. (although that might be worth a discussion on its own!)
Yes, proper support for booting multiple components, using the same Injector. This will bring much of the value from micro frontends without most of their usual costs.
Well, without a ngModule, the app component becomes the app, right?
Yes, please! |
Beta Was this translation helpful? Give feedback.
-
Just more conventions over configurations... I'll try to go to the extreme, just pushing boundaries a little bit :-).
2a)// just boostrap a simple app without providers.
bootstrap(AppComponent); 2b)// a real app with configuration defined using InjectionTokens,...
await bootstrap(AppComponent, [
// { provide: BOOTSTRAP_OPTIONS, useValue: {...} }
useBootstrapOptions({
ngZone: 'zone.js',
ngZoneEventCoalescing: true,
}),
// { provide: ROUTER_OPTIONS, useValue: {...} }
useRouterOptions({
initialNavigation: 'enabled',
}),
// { provide: ROUTES, useValue: [] }
useRoutes([]),
// ...
{ provide: ErrorHandler, useClass: MyErrorHandler },
{
provide: LOCALE_ID,
useValue: 'cs',
},
]);
console.log('App is running'); And thank you for such a great RFC. |
Beta Was this translation helpful? Give feedback.
-
Hi, this RFC is amazing! congrats! I want to know is if you are planning to support a "ComponentWithProviders" feature to configure standalone components, similar to the ModuleWithProviders for modules. @Component({
///.....
})
class DateTimeFormComponent{
static configure(localeId: string) : ComponentWithProviders {
return {
component: DateTimeFormComponent,
providers: [{provider: LOCALE_ID, useValue: localeId}]
}
}
constructor(@Inject(LOCALE_ID) localeId){}
}
@Component({
standalone: true,
///.....
imports: [DateTimeFormComponent.configure('fr')]
})
class MyParentComponent {} And may will be useful for routing too. @Component({
///.....
})
class ChildComponent{
static configure(data) : ComponentWithProviders {
return {
component: DateTimeFormComponent,
providers: [{provider: DATA_INJECTOR, useValue: data}]
}
}
constructor(@Inject(DATA_INJECTOR) data){}
}
// router
{
path: 'child-component',
loadChildren: () => import('./child-component').then(component=> component.configure({ config: "123" })),
} |
Beta Was this translation helpful? Give feedback.
-
Is it possible that Standalone Components are released before these APIs?Will you wait on figuring out these APIs before releasing the Standalone Components feature itself? Standalone Components will be useful even before the release of these APIs. |
Beta Was this translation helpful? Give feedback.
-
+1 for the default exports and separate injectors for a route. |
Beta Was this translation helpful? Give feedback.
-
BootstrappingThe new suggested bootstrapBrowserApp({
declarations: [RootComponent],
providers: [
{ provide: AuthService, useClass: JwtAuthService },
importProvidersFrom(RouterModule.forRoot([…], {…}))
]
}): Current With the change I now assume that in this case RoutingIn the case of route configuration extracted into separate files, I believe that adding a Could developers maybe import functions used in import { AService, BService} from "...";
import { transient, scoped } from "@angular/core";
@Component({
selector: 'lazy',
providers: [
transient(AService), // <- Will be destroyed when component is destroyed
scoped(BService) // <- Will remain in route provider tree and reused when component is created again
]
})
class LazyComponent {
constructor(private a: AService, private b: BService) {}
} This concept borrows a bit from .NET DI where you have Default exportI initially was very pro this idea, but after thinking for a while I do believe this "magic" doesn't really provide much value. The difference between {
path: 'admin',
loadChildren: () => import('./admin.routes').then(chunk=> chunk.ADMIN_ROUTES),
}
// and
{
path: 'admin',
loadChildren: () => import('./admin.routes');
} is so few extra characters of code, IMO being explicit in this case should not be a big deal, even in enterprise applications with hundreds of routes. This will probably help with debugging as well where you actually can provide the name of the exported variable in a stacktrace. |
Beta Was this translation helpful? Give feedback.
-
Impact of optional-NgModules on the next milestones in Angular roadmapI'd be happy to understand whether introducing the optional-NgModules can technically help Angular to move forward with the next milestones from the Angular roadmap, including the latest innovations in the web industry. Thank you for your hard work for constantly improving Angular! I'd like to share my impression (maybe wrong?) that Angular compiler is very sophisticated, which (I guess?) makes the adoption of the latest innovations from web industry more tricky. The support of dynamic-imports for example was added quite lately (comparing to Vue and React) when ViewEngine was rewritten to Ivy. And I'm wondering if dropping NgModules will significantly help to simplify the Angular compiler, so Angular can adopt new innovations in the web industry more eaisly. But again, maybe it's just my wrong impression. You folks are doing great job developing Angular, many thanks! The Angular roadmap touches - among others - the following 3 topics:
Moreover, on the horizon we can see the birth of the next generation of "resumable-SSR" freameworks (Marko and Qwik). There are some pending works to integrate Qwik with React. It's interesting when Angular will be able to adopt similar innovations. Do you think that any of above topics will be simpler to achieve for Angular compiler when NgModules are optional or totally removed? If yes, why? |
Beta Was this translation helpful? Give feedback.
-
First of all a great RFC and generally standalone components are a blessed change! One thing that was brought up in the previous RFC and I can't recall it being answered is the case of NgModules as logical grouping of related components. To be more precise - if a standalone component has some repeating pattern which fits to a new sub component - how can we make this new sub component privately usable only within the original component? With modules we would just omit it from the exports array. It seems in standalone components this is not feasible unless used in a library scope. I don't think exposing each and every component as a public standalone component even in apps is a good idea (same as private class methods). Any direction on how to solve it? Or is it only me that thinks it's a problem? |
Beta Was this translation helpful? Give feedback.
-
This RFC is just incredible -- thank you! -- and will clearly have a positive impact on many aspects, from developer experience to sustainability. I see Standalone Components as a great opportunity to improve the mental model of Angular applications. However, I feel like we are trying delegate a lot to the Router and I'm not convinced that it should be responsible for all. Maybe I'm totally wrong but why couldn't we keep some of those properties/metadata in the components themselves? It would allow us to:
I know this goes totally against the current project and router but like I said, this RFC is great opportunity to discuss. Example: // my-routed-component.ts
@Component({
path: 'something',
strategy: 'lazy' // 'eager'
children: [
MyChildrenComponent,
MyChildrenComponentFoo,
MyChildrenComponentBar
...
]
})
export class MyRoutedComponent {}
// routes.ts, if any
export const Routes = [
MyRoutedComponent,
MyRoutedComponentFoo,
MyRoutedComponentBar
...
] Where routed components may have slightly more properties than they do now but it makes sense to me that these belong to the component instead of the Router. Also, routed component don't need a And where Another example // my-route-config.ts
export const MyRouteConfig = [{
path: 'something',
strategy: 'lazy' // 'eager'
children: [],
...
}]
// my-routed-component.ts
@Component({
route: MyRouteConfig,
...
})
export class MyRoutedComponent {} I have this feeling where the RouterModule could totally lookup routes directly from the components so we don't have to declare them anywhere. This is me thinking outloud but as mentioned, I think this RFC is a good opportunity to discuss. |
Beta Was this translation helpful? Give feedback.
-
@alxhub @pkozlowski-opensource
There is some discussion in this RFC where some peole think this is "not the best idea"™️. I do disagree, and think it might be the case for those people and should be taken care of with an optioal lint rule. // In someModue:
export default const someThing = class myModule { /* code here */ }
// in the importing module
const myModule = import('someModule').then(m => m.someThing)
// Versus
const myModule1 = import('someModule')) They will both represent the same export. That made me curious. Is there a way to differentiate? EDIT: After rereading this, I thought it might come of as snarky. Sorry for that, that isn't the case. |
Beta Was this translation helpful? Give feedback.
-
I'm assuming this is what you mean by the constructor anti-pattern? Yes, it would be great if I could have a function that just runs this code so I don't have to do this sweet NgModule constructor trick. Import this module and I have NgIdle up and running.
|
Beta Was this translation helpful? Give feedback.
-
What about services that are:
I think that I missed something 🤔 Here is an example to make this clear Challenge: Wrapping non-treeshakable, or module-depending servicesThe following pattern is useful in order to hide implementation details (i.e. dependencies). @Injectable()
export class Notifier {}
@NgModule({
providers: [Notifier],
imports: [MatDialogModule, MatToastModule],
})
export class NotifierModule {} A common similar scenario is the facade pattern with state management so that we don't forget to import the feature store before using the facade. @Injectable()
export class MyFacade {}
@NgModule({
providers: [MyFacade],
imports: [StoreModule.forFeature(...)],
})
export class MyFacadeModule {} These modules could be used this way @Component({
standalone: true,
imports: [MyFacadeModule, NotifierModule]
})
export class MyComponent {} Possible full standalone (but not equivalent) solutions with the current RFCWhat would be the full-standalonish alternative? class Notifier { static providers = [Notifier, importProvidersFrom(MatDialogModule), ...]; }
class MyFacade { static providers = [MyFacade, importProvidersFrom(StoreModule.forFeature(), ...] }; but then, where should we provide them? a. in root injector with
|
Beta Was this translation helpful? Give feedback.
-
Demo repoHey, I set up this demo repo which uses the Standalone APIs preview (currently
|
Beta Was this translation helpful? Give feedback.
-
No, please don't add any magic. IDE will not recognize this convention, part of developers will be confused as well. |
Beta Was this translation helpful? Give feedback.
-
I've tried to convert one of my components (which is "scam") into standalone components and found this issue: I'm using NgRx ComponentStore, code initially was: @Component({
selector: 'scs-editor',
templateUrl: './editor.component.html',
styleUrls: ['./editor.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [EditorStore] /// 👈 will initialize a new EditorStore instance
})
export class EditorComponent {
public readonly state$: Observable<EditorState>;
public readonly textCtrl: FormControl<string | null>;
constructor(
private readonly store: EditorStore,
) {
this.state$ = this.store.state$;
this.textCtrl = this.store.textCtrl;
}
save() {
this.store.save$();
}
localModelDataLoaded(modelData: string) {
this.store.patchState({model: modelData});
}
}
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
MatInputModule,
MatButtonModule,
MatIconModule,
MatProgressSpinnerModule,
LocalStorageMenuComponentModule,
],
providers: [AppStore], /// 👈 will use existing AppStore instance
declarations: [EditorComponent],
exports: [EditorComponent],
})
export class EditorComponentModule {} I've replaced it with: @Component({
selector: 'scs-editor',
templateUrl: './editor.component.html',
styleUrls: ['./editor.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
providers: [EditorStore, AppStore], /// 👈 it's obviously not the same, how should I write it?
imports: [
CommonModule,
ReactiveFormsModule,
MatInputModule,
MatButtonModule,
MatIconModule,
MatProgressSpinnerModule,
LocalStorageMenuComponentModule,
],
})
export class EditorComponent {
public readonly state$: Observable<EditorState>;
public readonly textCtrl: FormControl<string | null>;
constructor(
private readonly store: EditorStore,
) {
this.state$ = this.store.state$;
this.textCtrl = this.store.textCtrl;
}
save() {
this.store.save$();
}
localModelDataLoaded(modelData: string) {
this.store.patchState({model: modelData});
}
} As you can see, I'm accessing the global state store using "providers" of NgModule and creating a local store using "providers" of the component. Maybe it's just some misunderstanding of the APIs - please let me know how to fix it :) Code runs fine, but the AppStore is not initialized ;) |
Beta Was this translation helpful? Give feedback.
-
tl;dr; thank you for all the feedback - our intention is to implement APIs described in this RFC and roll them out in developer preview for Angular v14! Shipping it as a developer preview in Angular v14!First of all, we would like to thank everyone who commented on, asked questions, or otherwise engaged in the discussion on this RFC. The high quality input from the community sparked lots of useful discussions and allowed us to make adjustments to the initial design. Based on all the comments and feedback we didn't find any use-cases or technical constraints that would "break" the design and / or prevent us from making Components and applicationsWe’ve noticed multiple discussion threads centered around the “application” vs. “component” responsibilities as well as dependency injection. We can clearly recognize the desire of shifting even more responsibilities to the components (ex. full DI configuration, including router configuration). But we believe that the concept of the "Angular application" is an important one and we want to preserve it - @alxhub wrote a detailed comment describing our thinking. We do hear loud and clear that the component / application distinction might not always be clear and we need to do better explaining and documenting the design. We also didn't plan to re-design the DI system as part of the "standalone" efforts. Angular always had two types of injectors (so-called "module" injector and "node" injector) and it continues to be the case with standalone components. Community feedbackWe'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:
Frequently asked questionsFinally, we’ve identified some recurring questions and would like to highlight answers here. Import package for
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Authors: @alxhub, @pkozlowski-opensource, @AndrewKushnir, @atscott
Area: Angular Framework
Posted: April 6, 2022
Status: Closed
A few months ago, we published an RFC for the design of standalone components, directives, and pipes. This is a project with an ambitious goal: to streamline the authoring of Angular applications by reducing the need for NgModules. The design was positively received by the community with over 140 discussion comments on the initial RFC.
This RFC complements that first proposal, and explores how standalone components will be integrated into Angular's API surface to achieve the goal of authoring applications without writing NgModules. It focuses on four areas where NgModules feature prominently today:
Goals
In designing these APIs, we focused on a handful of high-level goals:
Providers
A central role played by NgModules today is the configuration of dependency injection, and therefore of application and library functionality. Applications import NgModules, sometimes via helper functions such as
StoreModule.forFeature(…)
, to configure behavior. Among other effects, these NgModules serve as collectors and containers for DI providers.We believe that DI configuration can be cleanly achieved without NgModules, by working with providers and provider arrays directly. For example, today the Angular router is configured via
RouterModule.forRoot(routes, config)
, which accepts both the main routes for the application as well as a configuration object for the router. Both of these are then expressed in the application's DI configuration.Instead, the router could expose separate functions for configuring router behavior, and declaring routes. This could look like:
As we work to reduce the complexity of NgModules in Angular, we plan to guide the ecosystem towards a providers-first approach, and away from using NgModules as configuration containers.
New APIs: Bootstrapping
The traditional flow of bootstrapping an Angular application is to declare an
AppModule
that names the component to bootstrap. This process involves a lot of different operations, some required and some by convention:To support bootstrapping without an NgModule, a new function
bootstrapApplication
will be introduced. Instead of configuring the application via NgModules, both the root component andproviders
are specified directly:The
importProvidersFrom
function serves a key role here of allowing existing NgModule-based libraries to be configured in an application bootstrapped without anAppModule
. In the future, we plan to guide the ecosystem to transition to provider-based APIs for this kind of configuration.Fallback to NgModule-based bootstrap
For more advanced use cases than
bootstrapApplication
supports, we currently suggest falling back on using the NgModule-based bootstrap. This includes:NgZone
ngDoBootstrap
lifecycle hookNew APIs: Router and lazy loading
The Angular Router supports lazy loading of a "part" of the application, expressed as a separate NgModule. This application part serves two distinct purposes:
We believe these are two distinct use cases, and will decouple them in the router's new standalone APIs. This leads to three new capabilities for a route definition:
Lazy loading a single component
If a component is standalone, it can be lazily loaded directly for a route, without the need to declare any extra routing configuration or NgModule to load:
To use this API, the component being loaded must be standalone.
Lazily loading additional child routes
Today,
RouterModule.forChild([…])
is imported into a lazily loaded NgModule to configure child routes. When all routed components are standalone,loadChildren
can be used to directly load the additional routes instead:Separate injectors for a route
In some cases, it's desirable to scope a set of services to a route or collection of routes that represent a specific part of the application. For example, an
AdminService
could be scoped only to the admin routes of an application.A new
providers
option on a route allows that route to declare a set of providers which will be used to create an injector in the application injector hierarchy for that route and its children. For example, theAdminService
could be provided on a route:This would create a new injector for the
'admin'
route and its children, just as would happen if'admin'
were configured to useloadChildren
with a separateAdminModule
today.Lazily loading the injector
If the
'admin'
routes are lazily loaded, it may be desirable to lazily load the injector associated with them (like would happen using the NgModule API forloadChildren
today). This is straightforward to accomplish through composition of the two APIs:The lazily loaded route configuration contains a single parent route which configures the injector and declares a number of child routes.
Interop with NgModules
As with
bootstrapApplication
, it should be possible to consume existing NgModules from the new APIs. For example, today ngrx usesStoreModule.forFeature(…)
to lazily load additional functionality into the ngrx store. TheimportProvidersFrom
bridge is used to do this:Dynamic component instantiation
A central use case discussed in the initial standalone design is that of dynamically rendering a component. In Angular today, it's easy to write code that does so while ignoring the component's NgModule, risking problems at runtime if the component or any of its dependencies require special configuration via providers.
Setting the
standalone: true
flag for a component is an indication that the component itself doesn't require configuration from an NgModule, but the same cannot be said for the component's dependencies. For that reason, a new injector is created in the application injector tree whenever a standalone component is rendered dynamically (for example, via the router), which hosts the providers from that component's NgModule dependencies. This is called a standalone injector. Consider this example:The first time that the button is clicked, a new standalone injector is created (assuming one is needed), which hosts any providers from NgModule-based dependencies of
DynamicCmp
. This injector then becomes the parent injector of the newDynamicCmp
instance, just as ifDynamicCmp
was not standalone and correctly instantiated via its NgModule.The second time the button is clicked, the previous standalone injector can be reused. This serves several purposes:
Router
We expect that the need for these standalone injectors will be reduced over time, as libraries switch to non-NgModule based configuration and as we move further down the path of simplifying NgModules and reducing their responsibilities.
Initialization logic
An interesting behavior of NgModules today is that they're eagerly instantiated in application injectors. That is, when bootstrapping an application:
Both
AppModule
andFeatureModule
will be instantiated (with constructor injection). This makes NgModule constructors behave as de facto lifecycle hooks. This effect is used in some libraries to allow for new functionality to be loaded into existing services.The use of constructors for complex logic is not ideal (and is often considered an anti-pattern), and this pattern only works with NgModule-based configuration.
To support initialization logic without NgModules, an
INJECTOR_INITIALIZER
token will be supported by the application injector hierarchy.INJECTOR_INITIALIZER
s are functions which are run when the injector is created, and allow for eager instantiation and other setup logic to be executed. This example injects and signals to aLoadingService
that additional functionality has been loaded:The
importProvidersFrom
function will convert the eager initialization behavior of NgModules into anINJECTOR_INITIALIZER
for use in the new providers-based APIs.Default exports
The
loadChildren
and proposedloadComponent
APIs accept aPromise
to the type or data being loaded. They're designed to be used with a dynamicimport()
operation.import()
however returns aPromise
to the overall ES module (file) being imported, necessitating a.then
operation to pull out the desired type or value:Alternatively, we're considering extending these router APIs to recognize
default
exports automatically. With this functionality, the above could be written as:assuming that the
admin.routes.ts
file was set up accordingly:This could reduce boilerplate when expressing lazily loaded routes, but might create confusion since the dereferencing of
default
is done under the hood. We're interested in feedback on whether this functionality would be valuable or would be too surprising.Specific Questions
In addition to general feedback on the proposed new APIs, we're interested in specific feedback on the following:
bootstrapApplication
. Should we use another verb besidesbootstrap
? ShortenApplication
toApp
or drop it entirely?bootstrapApplication
support?default
exports in its lazy loading APIs?Beta Was this translation helpful? Give feedback.
All reactions