Closed pantonis closed 2 months ago
Here is great option to implement such feature, thanks to @kristofdegrave! https://github.com/angular/angular/issues/34351#issuecomment-577095385
when you're importing your module, you can do like this:
import('./some/some.module')
.then(({ SomeModule }) => {
// here you can do any async logic, and by using @kristofdegrave solution
// pass the result of operation as provider to your lazy module
return makeRequest().pipe(
map(YOUR_DATA_HERE) => new LazyNgModuleWithProvidersFactory<T>(SomeModule.forChild(YOUR_DATA_HERE))
).toPromise()
});
in your module:
export class SomeModule {
public static forChild(data: SOMEDATA): ModuleWithProviders<SomeModule> {
return {
ngModule: SomeModule,
providers: [{ provide: TOKEN, useValue: data }],
};
}
}
and in all module's components you'll be able to get your lazy loaded data by token you've provided
the same you can do with routes, define routes for module when you loading that exact module
providers: [{ provide: ROUTES, useValue: routes }], // where routes:Route[]
+1 for this This will be very important requirement with Module Federation. The microfrontend/plugin loaded using module federation can be a completely independent application which has to do so some initialisation. Considering the modules are loaded as lazy modules we can not use APP_INITIALIZERS
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
+1 for MODULE_INITIALIZER
Would love to have a MODULE_INITIALIZER too, adding my voice to this.
Why hasn't this been added yet?? +1 for this request! I want to run some asynchronous code when a module is lazy loaded, and right now cannot
Still miss this... https://stackoverflow.com/a/57626816/256646
Please implement this! Its really important e.g. to load external libs that cannot be bundled (i am thinking of a payment-provider lib etc) as a dependecy of a dynamic module
Hi all, what about routing Guard CanLoad
for lazy loading routes or CanActivate
for regular routes? You can implement your logic inside the guard before the module is loaded but always return from that guard Observable
CanLoad
and CanActivate
Guards are not made for this. As the names suggests they guard a route. They check if you can do it, but not to perform any mutation or do other logic.
CanLoad
andCanActivate
Guards are not made for this. As the names suggests they guard a route. They check if you can do it, but not to perform any mutation or do other logic.
@kristofdegrave Please, suggest your solution.
@AlexeyApplicature https://github.com/angular/angular/issues/34351#issuecomment-577095385 This is a workaroud, the real solution would be to have a hook on the router when a module gets loaded, and have the possibility to inject a ModuleWithProviders.
My workaround works but it needs code that shouldn't exist outside the framework
btw, this was my PR I proposed: https://github.com/angular/angular/pull/36084
Another vote to request this feature. My use case is for 'regular' modules, not even lazy loaded. Each module needs to load its resource bundles (help texts, labels, etc.) before it is ready for use.
It would be nice to extend the concept to @Directive, maybe with a new type of PromiseInjectionToken, where angular automatically waits for the promise to resolve before injecting that parameter. That way appropriate instance variables can be initialized directly in the constructor and declared readonly.
P.S. The PromiseInjectionToken is much more general purpose and even removes the need for a special APP_INITIALIZER
just to add my use case in here...
so I have a workflow where a user authenticates, and then once logged in their configuration is requested over HTTP.
this configuration is used to build the full routing tree of all pages and sub-pages in the application which they have access to.
without knowing the configuration, the full set of routes are not known.
so it would be this scenario here: https://stackoverflow.com/a/57626816/1061602
Just want to share, if your module is lazy loaded, you can hook between the loading and actual delivery to do any async initialization like this:
loadChildren: () => Observable.fromPromise(import('./fall-apart-layout/fall-apart-layout.module'))
.pipe(
switchMap((i) => {
return navigationService.$routesLoaded
.pipe(
map(() => i)
);
})
)
.toPromise()
.then(m => m.FallApartLayoutModule),
With that in mind an playing with injection, you can get any bootstrap data you need for a dinamically loaded module that you can even use to provide the router module with dinamically defined routes.
Then in the dinamically loaded module, use a factory to provide the routes instead of calling "RouterModule.forChild":
imports: [
{
ngModule: RouterModule,
providers: [
{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
multi: true,
useFactory: getRoutes,
deps: [NavigationService]
},
{
provide: ROUTES, multi: true, useFactory: getRoutes, deps: [NavigationService]
},
]
}
@david-garcia-garcia this is very nice, thanks for sharing!
I was able to do it with empty ROUTES
token
@NgModule({
imports: [EffectorModule.forFeature([DocumentListEffect])]
})
export class DocumentsModule {}
import { NgModule, ModuleWithProviders, Type } from '@angular/core';
import { ROUTES } from '@angular/router';
@NgModule({})
export class EffectorModule {
static forFeature(
featureEffects: Type<any>[] = []
): ModuleWithProviders<EffectorModule> {
return {
ngModule: EffectorModule,
providers: [
...featureEffects,
{
provide: ROUTES,
useFactory: () => () => [],
deps: featureEffects,
multi: true
}
]
};
}
}
+1 for adding this into Angular
still missing this feature
+1 when you need to read settings and module federation is involved, it is a necessary feature, I hope it will be added as soon as possible.
+1 Need this feature
We can see that this feature request generates lots of interest which means that there are legitimate and common use-cases that are / were not not well supported. I was reading and re-reading this issue today (as well as associated issues) and if my understanding is correct, we are mostly talking about use-cases where we need to lazy-load some form of configuration before instantiating a DI service.
It is true that Angular's DI system doesn't offer any solution in this respect (there is a tracking request #23279 to add async capabilities to our DI system, but this would require a major overhaul / re-design of the DI system). But we can do lots of things before DI kicks in!
More specifically, with all the changes done in v14 (and more specifically - with the loadChildren
and providers
changes for the router) we can:
NgModule
,If we combine the above functionality we can come up with a pattern where we can lazy-load all the needed configuration and then return router + providers configuration. The gist of it could look like:
export async function lazyRoutes() {
const config = await import('./lazy-config');
return [
{
path: '',
providers: [
{ provide: LazyService, useFactory: () => new LazyService(config) },
],
component: LazyComponent,
},
];
}
Such functions could be then used in a router configuration:
RouterModule.forRoot([
{
path: 'lazy',
loadChildren: () =>
import('./lazy-with-config/lazy-routes').then((m) => m.lazyRoutes()),
},
]),
Here is a working stackblitz: https://stackblitz.com/edit/angular-ivy-e2dmmp?file=src%2Fapp%2Fapp.module.ts
With this approach we don't really need any NgModule
and all the async logic is contained in regular async JavaScript functions. I do believe that this is a much simpler pattern.
With all the changes we've been doing to Angular lately we move towards the World where NgModule
s are optional and play less prominent role. As such we don't want to invest in adding more functionality to NgModule
s and it is very unlikely that we would want to implement MODULE_INITIALIZER]
. Again, this is based on the assumption that simpler solutions exist today.
I was trying to understand the described use-cases to my best ability but I can't exclude that I've missed some important scenarios. If you still see use-cases that are not well covered today, please add your comment in this issue. But if we don't discover anything new here I'm leaning towards closing this issue as solved by all the recent v14 changes.
@pkozlowski-opensource from what you present, it should support the use-cases I had. I have been out for a while because I changed customer. I Think angular is taking some good steps with making NgModule optional. On the other side I'm a fan of working with modules, because lazy loading modules looks for me as a better fit then lazy loading every 'smart' component separately. I have been working with Vue and that is the case there. But the thing I missed there the most was a way to lazy load modules including child routing. When going to Micro Frontends, this is the level I would need.
@pkozlowski-opensource Thanks for the reply. Unfortunately I'm not sure how this helps, consider the following scenario:
To use firebase I need to call provideFirebaseApp(() => initializeApp(firebaseConfig))
inside of the imports of my AppModule. How can I do this if firebaseConfig must be loaded asynchronously?
The reason I want to load the firebaseConfig asynchronously is because I want to seperate config from artifact, to be able to have a clean CI/CD pipeline (build once, run on any environment, get config from server).
What I would like to do:
let config: { firebase: any };
function initializeAppFactory(httpClient: HttpClient): () => Observable<any> {
return () => httpClient.get('https://myserver.com/api/config').pipe(c => config = c)
}
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
provideFirebaseApp(() => {
return initializeApp(config.firebase);
// THIS DOES NOT WORK, because the APP_INITIALIZER is async! How can we also make this import async?!?
})],
declarations: [AppComponent],
bootstrap: [AppComponent],
providers: [{
provide: APP_INITIALIZER,
useFactory: initializeAppFactory,
deps: [HttpClient],
multi: true,
}],
})
export class AppModule {}
Nice to have feature
We can see that this feature request generates lots of interest which means that there are legitimate and common use-cases that are / were not not well supported. I was reading and re-reading this issue today (as well as associated issues) and if my understanding is correct, we are mostly talking about use-cases where we need to lazy-load some form of configuration before instantiating a DI service.
It is true that Angular's DI system doesn't offer any solution in this respect (there is a tracking request #23279 to add async capabilities to our DI system, but this would require a major overhaul / re-design of the DI system). But we can do lots of things before DI kicks in!
More specifically, with all the changes done in v14 (and more specifically - with the
loadChildren
andproviders
changes for the router) we can:
- dynamically load router configuration without any
NgModule
,- specify providers on the lazy-loaded routes.
If we combine the above functionality we can come up with a pattern where we can lazy-load all the needed configuration and then return router + providers configuration. The gist of it could look like:
export async function lazyRoutes() { const config = await import('./lazy-config'); return [ { path: '', providers: [ { provide: LazyService, useFactory: () => new LazyService(config) }, ], component: LazyComponent, }, ]; }
Such functions could be then used in a router configuration:
RouterModule.forRoot([ { path: 'lazy', loadChildren: () => import('./lazy-with-config/lazy-routes').then((m) => m.lazyRoutes()), }, ]),
Here is a working stackblitz: https://stackblitz.com/edit/angular-ivy-e2dmmp?file=src%2Fapp%2Fapp.module.ts
With this approach we don't really need any
NgModule
and all the async logic is contained in regular async JavaScript functions. I do believe that this is a much simpler pattern.With all the changes we've been doing to Angular lately we move towards the World where
NgModule
s are optional and play less prominent role. As such we don't want to invest in adding more functionality toNgModule
s and it is very unlikely that we would want to implementMODULE_INITIALIZER]
. Again, this is based on the assumption that simpler solutions exist today.I was trying to understand the described use-cases to my best ability but I can't exclude that I've missed some important scenarios. If you still see use-cases that are not well covered today, please add your comment in this issue. But if we don't discover anything new here I'm leaning towards closing this issue as solved by all the recent v14 changes.
I think you've missed a key scenario here. We're using micro-frontends via module federation. A typical example of one of our micro-frontend apps has 2 entry points: the standard, bootstrapped AppModule and also a RemoteEntryModule. so that the application can be run standalone (both for production and development purposes) and as a remote module for a legacy application. The AppModule is just a shell to import the routermodule root and BrowserModule, and finally importing the "real" application module which is RemoteEntryModule. Before RemoteEntry loads, I have a configuration service that makes an HTTP request to request a json configuration. I would prefer to use Angular's HttpClient which has to be provided via DI, I can't do what I need to do without hacky workarounds. And each application has to get its own configuration, I can't lump all of that within our federation host (the legacy app). Module initialization would be great for this. Also upgrading to 14 isn't possible at the moment for us.
Have a similar use case where I have a 3rd party library that uses APP_INITIALIZER
to init itself which is not working for lazy loaded feature modules.
I understand that this library probably was designed to be used as an eagerly loaded module, but I don't see big reasons why it shouldn't work with lazy loaded feature modules and theoretical MODULE_INITIALIZER
.
Unfortunately, the following workaround can't be used as a solution to refactor this 3rd party library
RouterModule.forRoot([
{
path: 'lazy',
loadChildren: () =>
import('./lazy-with-config/lazy-routes').then((m) => m.lazyRoutes()),
},
]),
because lazyRoutes()
function here is not DI-aware, but the problematic library uses DI in APP_INITIALZER
provider like that
export function startupServiceFactory(alfrescoApiService: AlfrescoApiService) {
return () => alfrescoApiService.load();
}
...
providers: [{
provide: APP_INITIALIZER,
useFactory: startupServiceFactory,
deps: [
AlfrescoApiService
],
multi: true
}],
What about something similar to NestJS? Nest provides onModuleInit
hook which is awaited before services and controllers (components) onInit methods. They can be used inside Modules.
@NgModule({
...
})
export class MyModule implements OnModuleInit {
async ngOnModuleInit() {
await ....
// runs before injectables/components ngOnInit
// and before "child" modules OnInit and its awaited ...
}
}
I need the same functionality for firebase initialization from remote configurations. The "MODULE_INITIALIZER" looks like the most idiomatic way for that feature in Angular.
@patricsteiner have to found a workaround for that use case?
Eventually I solved it by providing FIREBASE_OPTIONS token in appModule
{
provide: FIREBASE_OPTIONS,
useValue: environment.firebase
},
and in environment.ts I have
{
...
firebase: getConfig()
}
getConfig relies on synchronous XMLHttpRequest.
Works fine.
+1 This is important for module federation integration between two applications
I recently needed an initializer to run when a lazy-loaded module is loaded, and @pkozlowski-opensource 's example wasn't sufficient, but the general idea that there are more extension points than there used to be was helpful.
I still think a MODULE_INITIALIZER
would be useful, but here's an updated analysis of what we currently can do (ng 15-16):
1. Async import continuation in loadChildren
loadChildren: () => import('./lazy/lazy-routes').then((m) => m.lazyRoutes()),
lazyRoutes()
has to be a function or static class method that returns a Route[]
. It may be async. The downside is that it
can't use inject()
or otherwise obtain dependencies.
If you need inject()
(I did) you can:
2. Factory function for ROUTES
The old recommendation for lazy-loaded route modules was using RouterModule.forChild()
:
const routes: Routes = [{ ... }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class CustomersRoutingModule { }
If you look at forChild()
, it's pretty simple:
static forChild(routes: Routes): ModuleWithProviders<RouterModule> {
return {
ngModule: RouterModule,
providers: [{provide: ROUTES, multi: true, useValue: routes}],
};
}
So, you can replace the call to forChild()
with a factory fn for ROUTES
and an import for RouterModule
. Here's an example, which calls inject()
to initialize a service before returning the routes:
const routes: Routes = [{ ... }];
@NgModule({
imports: [RouterModule],
exports: [RouterModule],
{
provide: ROUTES,
multi: true,
useFactory: () => {
// initialize the CustomerLoggingService before any of the routes are used.
inject(CustomerLoggingService);
return routes;
}
},
})
export class CustomersRoutingModule { }
The downside of this approach is that the initialization function cannot be async (or at least you can't wait for an async initialization fn to complete).
If you need an async initializer in a lazy-loaded module with dependency injection, I would use one of the route guards. You'll have to deal with the fact that guards can be called whenever the route is tested, so you'll have to protect against repeated initialization. Both of the suggestions I've provided here have the advantage that they're only called once.
We can see that this feature request generates lots of interest which means that there are legitimate and common use-cases that are / were not not well supported. I was reading and re-reading this issue today (as well as associated issues) and if my understanding is correct, we are mostly talking about use-cases where we need to lazy-load some form of configuration before instantiating a DI service.
It is true that Angular's DI system doesn't offer any solution in this respect (there is a tracking request #23279 to add async capabilities to our DI system, but this would require a major overhaul / re-design of the DI system). But we can do lots of things before DI kicks in!
More specifically, with all the changes done in v14 (and more specifically - with the
loadChildren
andproviders
changes for the router) we can:
- dynamically load router configuration without any
NgModule
,- specify providers on the lazy-loaded routes.
If we combine the above functionality we can come up with a pattern where we can lazy-load all the needed configuration and then return router + providers configuration. The gist of it could look like:
export async function lazyRoutes() { const config = await import('./lazy-config'); return [ { path: '', providers: [ { provide: LazyService, useFactory: () => new LazyService(config) }, ], component: LazyComponent, }, ]; }
Such functions could be then used in a router configuration:
RouterModule.forRoot([ { path: 'lazy', loadChildren: () => import('./lazy-with-config/lazy-routes').then((m) => m.lazyRoutes()), }, ]),
Here is a working stackblitz: https://stackblitz.com/edit/angular-ivy-e2dmmp?file=src%2Fapp%2Fapp.module.ts
With this approach we don't really need any
NgModule
and all the async logic is contained in regular async JavaScript functions. I do believe that this is a much simpler pattern.With all the changes we've been doing to Angular lately we move towards the World where
NgModule
s are optional and play less prominent role. As such we don't want to invest in adding more functionality toNgModule
s and it is very unlikely that we would want to implementMODULE_INITIALIZER]
. Again, this is based on the assumption that simpler solutions exist today.I was trying to understand the described use-cases to my best ability but I can't exclude that I've missed some important scenarios. If you still see use-cases that are not well covered today, please add your comment in this issue. But if we don't discover anything new here I'm leaning towards closing this issue as solved by all the recent v14 changes.
The main issue with this is that it requires routing... I do not have routing in my case..
My use case doesn't have anything to do with routing configuration. I'm trying to eager load data (via 1 or more service calls) when the module is initialized that one or more components in the module will use at some point later.
I've begun calling the services directly from each module's constructor as I don't have another choice here using fire and forget, the service caches the data. Calling it from the component is too late as the user would then have to wait for the data retrieval. It's worked out well for us but feels very dirty and anti-Angular.
My use case doesn't have anything to do with routing configuration. I'm trying to eager load data (via 1 or more service calls) when the module is initialized that one or more components in the module will use at some point later.
Why not start this logic from a service's constructor in that case?
@pkozlowski-opensource the services are provided in root. I think we'd then be loading more data than needed which is why we wanted to wait to load them until the module was being loaded, though maybe loading all that extra data is not necessarily a bad thing and an interesting idea.
I think we'd then be loading more data than needed which is why we wanted to wait to load them until the module was being loaded, though maybe loading all that extra data is not necessarily a bad thing and an interesting idea.
Root or not, services are not created eagerly. So constructors would be called only if there is a class that injects them (which I would assume is a strong indication that something needs those data).
Could you give it a try?
Issue: Facing same issue, while trying to load a remote app through module federation. Does not make sense to include all the peer dependencies in the remote root module.
Angular version: 13.3.11 Module Federation version: 13..0.1
Request: While using angular with module federation, The providers should also be loaded with lazy loaded module rather than root module.
Any updates on this topic? I can see the issue is neither closed nor evolved.
Wow, I just rediscovered this issue, but we've had the fix in for a while now.
Meet ENVIRONMENT_INITIALIZER
:)
I'm submitting a ...
I was wondering if like APP_INITIALIZER a MODULE_INITIALIZER can be implemented. I have a scenario where multiple lazy load modules exist. Each module has a service that has injected in its constructor a config of type ConfigA. ConfigA is fetched from server. This service is then injected into several module components. With APP_INITIALIZER I cannot do this since the same type ConfigA is used for all modules and a singleton will be created.