Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass configuration data to services in a lazy loaded module #34351

Closed
kristofdegrave opened this issue Dec 11, 2019 · 18 comments
Closed

Pass configuration data to services in a lazy loaded module #34351

kristofdegrave opened this issue Dec 11, 2019 · 18 comments
Labels
area: core Issues related to the framework runtime area: router feature: insufficient votes Label to add when the not a sufficient number of votes or comments from unique authors feature: votes required Feature request which is currently still in the voting phase feature Issue that requests a new feature
Milestone

Comments

@kristofdegrave
Copy link

馃殌 feature request

Relevant Package

This feature request is for @angular/core or @angular/router

Description

I have an angular Application and a separate library which contains a feature. This feature is Lazy Loaded.

In that feature I have a service which needs some configuration that is environment dependent. So this information is present in the environment.ts files of the application.

Now I'm looking for a way to pass this environment information to my lazy loaded feature library. These services should only be accessible inside this module or its decedents.

Describe the solution you'd like

I was thinking about a similar solution as the forRoot or the forChild methods that currently exists.

@NgModule({
   imports: [
      RouterModule.forRoot([{
         path: 'feature',
         loadChildren: () => import('path/to/feature').then(module => module.FeatureModule.forChild(environment.whateverConfigSetting))
       }])
   ]
})
export class AppModule {}

Describe alternatives you've considered

An alternative solution I currently use is that I have created an addition library with a module that implements a forRoot method. And the module.forRoot() of this library is added to the imports. of the AppModule. Disadvantage of this is that the services is now exposes to the whole application.

An other alternative solution could be to inject a AppSettings service into the service living in the feature library and fetch the environment settings of this service. Disadvantage of this solution is that for every feature library I additionally add I have a tight coupling to that service.

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Dec 11, 2019

@kristofdegrave You can use dynamic import(...) to load a lazily-loaded module and during instantiating of that module, you can pass an injector, which you can modify before (add new tokens, ...), so you have those injections available in constructors of any part of such a lazily-loaded module.

@kristofdegrave
Copy link
Author

kristofdegrave commented Dec 11, 2019

@mlc-mlapis Can you please provide a little example of that, I don't really get what you mean.

@mlc-mlapis
Copy link
Contributor

@kristofdegrave Principally like this: #31886 (comment)

@AndrewKushnir AndrewKushnir added area: core Issues related to the framework runtime area: router feature Issue that requests a new feature labels Dec 11, 2019
@ngbot ngbot bot modified the milestone: Backlog Dec 11, 2019
@kristofdegrave
Copy link
Author

@mlc-mlapis I found that issue before I filed this one. But that thread focuses on loading modules outside the router, it mimics the behavior of what the router does with the advantage you can also inject providers.

What I am looking for is a solution with the router. I just want a decoupling between my feature libs and my Applications, but those feature libs need to run inside a context of an application, and that context needs to deliver some configuration to my feature libraries.

@mlc-mlapis
Copy link
Contributor

@kristofdegrave OK, using only the router way is a problem. Theoretically, you can nest the one into the other, but it's a question if it's not too complicated.

@kristofdegrave
Copy link
Author

I think I found a possible solution on how to handle this.
I have seen that the loadModuleFactory (which handles the loadChildren) in the router accepts an instance of thar NgModuleFactory.

private loadModuleFactory(loadChildren: LoadChildren): Observable<NgModuleFactory<any>> {
if (typeof loadChildren === 'string') {
return from(this.loader.load(loadChildren));
} else {
return wrapIntoObservable(loadChildren()).pipe(mergeMap((t: any) => {
if (t instanceof NgModuleFactory) {
return of (t);
} else {
return from(this.compiler.compileModuleAsync(t));
}
}));
}
}

With that in my mind I implemented an own implementation of the NgModuleFactory which accepts a ModuleWithProviders. https://gist.github.com/kristofdegrave/518622c22c6bd6a65382d9365e965d84

Using this implementation I can do the following in my routes:

RouterModule.forRoot([{
   path: 'feature',
   loadChildren: () =>
      import('path/to/feature').then(module => 
         new LazyNgModuleWithProvidersFactory(
            module.Feature.forRoot('Feature base url')))
}])

Some feedback on the solution would be nice, since my knowledge about the injector and compiler is limited, I don't know if I'm doing things I shouldn't.

@Wilt
Copy link

Wilt commented Dec 16, 2020

"Some feedback on the solution would be nice"

Would also be nice for others ending up here looking for a similar solution. I have parent modules lazy loading some feature modules and depending on the parent some things need to be configured differently, would be great to see some feedback on what @kristofdegrave proposed here.

@kristofdegrave
Copy link
Author

kristofdegrave commented Dec 16, 2020

@Wilt No feedback received, but the proposed solution with the LazyNgModuleWithProvidersFactory is something that works.
I Also added a PR for this behavior, but it has been rejected: #36084

@the-ult
Copy link

the-ult commented Mar 25, 2021

This seems like something a lot of projects could use. Using Configs/InjectionTokens for lazy loaded routes. So you can keep your feature-modules/libs clean.

But somehow it is hard to find any clean solutions. And it is still doesn't seem to be possible with the lazy routes itself => So it makes me wonder.. what are we doing wrong 馃 馃檭

So what would be a proper/better way to solve this?

Instead of:

TasksModule.forRoot({
      config: {
        domains: MyDomains,
      },
    }),

doing something like this would be great.

[
  {
     path: 'tasks',
     loadChildren: async () => (await import('@my-lib/tasks')).TasksModule.withConfig(
        config: {
          domains: MyDomains,
        },
     ),
  }
]

Using a 'global' ConfigService is a solution. But in that case, the ConfigService has to know to much about feature modules. And feature modules would need to inject the same ConfigService (High Coupling)
So it doesn't feel like a proper solution.

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Mar 25, 2021

@the-ult Did you mean using forRoot?

[
  {
     path: 'tasks',
     loadChildren: async () => (await import('@my-lib/tasks')).TasksModule.forRoot(
        config: {
          domains: MyDomains,
        }
     )
  }
]

@the-ult
Copy link

the-ult commented Mar 25, 2021

Yes. using forRoot or in this example I renamed it to withConfig
Since forRoot() is/was more the convention for inside imports[]. So I figured, this might be more appropriate 馃槆

But the above example does not work like that.. does it? e.g.
using the same forRoot() syntax, but within the loadChilderen? Or can you?

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Mar 25, 2021

@the-ult Never tried. It's normally a static method declared inside that module. If you tried it and it didn't work, it would mean that it's not invoked in the case of loadChildren, but it's invoked and processed correctly during imports (because the compiler understands what it means).

@the-ult
Copy link

the-ult commented Mar 25, 2021

Followed this article. Which seems to be working 馃憤
https://volosoft.com/blog/how-to-configure-angular-modules-loaded-by-the-router

But since it is so difficult to get it done and there is hardly anything found about providing config to a lazy loaded feature module... I'm still wondering what would be a better way to provide config to a feature module 馃 => Or shouldn't we do that at all?

@mgechev, or @IgorMinar do you have any solutions/ideas/insights?

@mlc-mlapis
Copy link
Contributor

@the-ult That example should be improved a bit to work also in AOT mode and not only in JIT because this code const factory = compiler.compileModuleSync(this.moduleType);.

@angular-robot angular-robot bot added the feature: votes required Feature request which is currently still in the voting phase label Jun 4, 2021
@angular-robot
Copy link
Contributor

angular-robot bot commented Jun 4, 2021

Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.

Find more details about Angular's feature request process in our documentation.

@angular-robot
Copy link
Contributor

angular-robot bot commented Jun 26, 2021

Thank you for submitting your feature request! Looks like during the polling process it didn't collect a sufficient number of votes to move to the next stage.

We want to keep Angular rich and ergonomic and at the same time be mindful about its scope and learning journey. If you think your request could live outside Angular's scope, we'd encourage you to collaborate with the community on publishing it as an open source package.

You can find more details about the feature request process in our documentation.

@angular-robot angular-robot bot added the feature: insufficient votes Label to add when the not a sufficient number of votes or comments from unique authors label Jun 26, 2021
@mgechev
Copy link
Member

mgechev commented Jul 17, 2021

I'll close this issue since it didn't collect a sufficient number of votes and doesn't align with an existing project on the roadmap.

@mgechev mgechev closed this as completed Jul 17, 2021
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Aug 17, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime area: router feature: insufficient votes Label to add when the not a sufficient number of votes or comments from unique authors feature: votes required Feature request which is currently still in the voting phase feature Issue that requests a new feature
Projects
None yet
Development

No branches or pull requests

6 participants