New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MODULE_INITIALIZER like APP_INITIALIZER #17606
Comments
Could you please describe your use case more precisely ? How do you lazy load your module ? |
Hi, Please see the sample code I created above that is implemented with resolver and is not working
|
APP_INITIALIZER works but I have to load all modules configurations in the RootModule. This raises security and isolation concerns as Feature module A will know the config of Feature module B, Feature Module A will know config of Feature module C and so on... |
... hmm, do you have a plunker that implements the resolver? Because in your code I can't se where you use the resolver service. It should be related to a route ... I can't see any route definition. There should be something like:
|
Yes I Have this and forgot to add this in the code above. |
... for sure, it is logical. What to think using some identification of the module and then inject that value to the resolver service to know what module is processed and how it should be processed:
|
No I dont like the idea with passing module id etc.. |
Maybe yes, in some cases but actually you don't have it and doesn't seem that someting like that will get the support in near future. As I know there should be something called |
@mlc-mlapis how do you know that it doesnt seem that will get the support in near future? |
Sure. The intention wasn't to say anything against, just a guess. 👀 |
Is there any viable workaround? |
@leak ... and |
Constructor kicks in too early for me.
Even when I'm loading the LoginPageComponent the Home module constructor is called. What I need is some sort of life cycle hook which only kicks in when the Home module is actually active. My actual use case: Loading a user profile. One way is through the login page, but when the auth is already good and the app is loading in another browser tab, I don't want to go through the login component again and rather just fetch the user profile from an api. Maybe I'm completely on the wrong train, I just started digging into this issue. |
@leak ... oh, I thought |
Just tested again to confirm:
Sadly it shows up on console when I start the application on /login. |
@leak ... ah, you have that module as a default route ... so no surprise that it is loaded immediately ... |
Nice catch. Leaves me short of the ctor option though. Guess I have to resort to dabbling with routing events... |
I also like the Idea of having a MODULE_INITIALIZER similar to APP_INITIALIZER very much, because in the module constructor there is no way to wait for async operations, while APP_INITIALIZER may return a promise that is resolved before going on. |
I'm looking for the same feature, I've to run some initialization code when the lazy module loads. |
This comment has been minimized.
This comment has been minimized.
+1 for MODULE_INITIALIZER. I need to populate the store in my feature module with query parameters from the url, currently I am doing it using APP_INITIALIZER but this means the root module needs to know the correct action to dispatch to the feature which feels wrong. An initialiser hook when the lazy module is loaded seems a reasonable request. |
MODULE_INITIALIZER is a good way to keep the separation of concerns. Example: in my application, there are 10 lazy loaded modules and each module has their configurations. Loading all configurations on App_Load will increase app load time. Also, if a user never loads a particular lazy loaded module, then all configurations loaded for these modules will be useless. MODULE_INITAILIZER is very much required. |
Would also be good for lazy loading global styles/scripts when navigating to a lazy loaded module, global styles/scripts that are only necessary for that particular module. |
Oh no, I was hoping this was available (my root module needs to load some local config for authentication) then, I need to load extra data on child modules,MODULE_INITIALIZER would have been the perfect solution for this |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
+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
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 With all the changes we've been doing to Angular lately we move towards the World where 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. |
@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 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 |
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. |
Have a similar use case where I have a 3rd party library that uses 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 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 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 @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
and in environment.ts I have
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 1. Async import continuation in loadChildren: () => import('./lazy/lazy-routes').then((m) => m.lazyRoutes()),
If you need 2. Factory function for The old recommendation for lazy-loaded route modules was using const routes: Routes = [{ ... }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class CustomersRoutingModule { } If you look at static forChild(routes: Routes): ModuleWithProviders<RouterModule> {
return {
ngModule: RouterModule,
providers: [{provide: ROUTES, multi: true, useValue: routes}],
};
} So, you can replace the call to 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. |
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. |
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. |
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 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? |
Wow, I just rediscovered this issue, but we've had the fix in for a while now. Meet |
Wow, that is awesome!! Since the Angular docs are a wee bit spare, here's an article on the topic: There's no mention of |
This is a nice feature, but unfortunately, this is not working as APP_INITIALIZER, we need a solution as we have for APP_INITIALIZER, where we can initialize the module after we run any function like an observable. This injector will initialize the module without waiting for this injector to finish. |
yeap, const ENVIRONMENT_INITIALIZER: InjectionToken<readonly (() => void)[]>; that is why I'd expected const APP_INITIALIZER: InjectionToken<readonly (() => void | Observable<unknown> | Promise<unknown>)[]>; |
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.
The text was updated successfully, but these errors were encountered: