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
Destroying lazy loaded modules doesn't unload them #24962
Comments
I'ved digged in the code a bit. When the lazy route is visited for the first time, it will take the string or callback of loadChildren and load the lazy module. If the _loadedConfig is present, it assumes the module is loaded and will retrieve it. So I was scouting for a work around and I have one problem and one blank:
I find it really weird that the instances aren't getting truly destroyed, they're set in a destroyed state and destroy() is called on the underlying providers but my application seems mostly unaffected (except for whatever happens in ngOnDestroy). |
@kewde ... what is the idea to destroy already loaded modules? Because the effect would be ... loading it once again from a server. Angular has no public API for destroying already loaded modules. JavaScript has GC and it cleans any memory object which is not already referenced. You can't access private methods and properties from Angular internal API. |
It wouldn't destroy the module as in requiring a reload, but it would destroy the instance and all the data in it. Angular does have a public API for destroying NgModuleRef's (https://angular.io/api/core/NgModuleRef). I expected that if I get a reference to the lazy loaded NgModule and destroyed it, it would recreate a new instance when revisiting the route. With no data left from the previous session. "You can't access private methods and properties from Angular internal API.". |
@kewde ... you would have a problem in AOT app ...
Ah, you are right. I forgot about it. And you mean that once instantiated singleton services are kept permanently?
|
The route isn't aware that the NgModule is destroyed (it still sits there in the array, just marked as destroyed), so the DI is always returning that module and its providers (read: lazy-singleton at the lazymodule level, so not really an app wide singleton). The data structure is completely wiped though. I have some custom initialization logic in the constructor of my lazy module, but that constructor isn't being called anymore because no new instance is being made. |
This is the log you'll see:
This is the log I expected:
|
@kewde ... ah I see now, so the behavior is somehow similar to using I didn't know that it's also possible to use |
@mlc-mlapis |
@kewde ... yep, we DI on modules. That's fine. But I am still thinking about the real used cases with |
…nce, creates new one instead (angular#24962)
…nce, creates new one instead (angular#24962)
…nce, creates new one instead (angular#24962)
…ates new one instead (angular#24962)
…ates new one instead (angular#24962)
@kewde can you explain me how to call destroy() on a lazy loaded module ? I cannot find any documentation about that. |
We get our lazy module:
Then we destroy it:
Then to set the user based on the route:
|
Thank you @kewde
Any idea? |
Route to a location outside of the lazy module before destroying it.
|
+1 would love to be able to completely destroy the lazy loaded module, we do run some initialization logic and "reprovide" or "shadow" some services in the lazy loaded module injector via useFactory, but seems like all these factory functions are memoized and never called again, tried to destroy lazy loaded module with the api route mentioned above, but no luck. |
I'm facing the same problem, there is some update about how to fix this? |
Looking into this at the moment. I am currently lazy loading a complete Map Module which I would like to clear once logged out. The reason behind this is because if the user re-logs in id like the MapModule can re-initialise all it's services etc without a page load. Unless there is a way to force some sort of re-initialization of a lazy module but not have it as it's the default action on every route change. Ryann. |
We would like to re-initialize services provided in lazy module too. #28405 looks like what we need. Is the fix being planned? |
Hello, |
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. |
When you have a hammer, everything is a nail. Lazy loading is not for caching service data. Lazy loading is for loading code in smaller bundles. You should be using design patterns like memo, visitor, command or a dictionary pattern in a service to store your data. Alternatively, you should use sessionStorage, or localStorage. Data is data. You just need a singleton service that controls. it. If you need different levels of access, you should partition the data into subobjects. JS/TS DOES actually destroy objects when you use them properly. If you like, you can even add an interface to access the subobjects from secondary services so it seems like the data is partitioned separately (adapter, facade pattern). The trick to solving these things is to use the hammer that needs the simplest nail. And if you feel you're going in circles from trying to use the hammer at hand, you should at least try to ask yourself if you have the right tool for the job. Lastly, if you TRULY MUST reload the services, use a window.location.href to reload the application. That will dump the entire content and start over. Thoughts? P.S. The ONLY reason I would want to dump a lazy loaded module is to remove the unused code. Data should not be controlled by module for a single session unless extenuating circumstances require it. And even then, it's better to store that kind of data in an offline data store and retrieve it when needed. |
I disagree. The point here is to be able to control the lifecycle of modules (and services), Angular does not yet offer this ability. The op gave here one use case, but I have another one that causes me issues too, I have to manually reset some service provided by a lazy module because they are not destroyed once loaded. We should be able to control this as in Dagger or other DI system. |
@ShamimIslam I globally agree with you about : "If you need to destroy a lazy loaded module to clear your data / avoid memory leak, your problem is elsewhere". But I still want this feature because I have the following use case : I have a huge "macro app" which assemble different tools for all the different job in the same company. Each employee is allowed to use some of the tools. I have a kind of plugin system (based among other thing on lazy loading via the router) where each tool is a plugin / NgModule and each time a user log in, based on the user permissions, I'm loading only the allowed tools. But when the user logout, I specifically need to unload all the tools because the next user to login might not have the same permissions and therefor will not load the same tools. In my case, it's not about clearing data, it's about "clearing / unloading code OR destroying instances" the next user will not be allowed to use. |
@epieddy There is the destroy() method that can be used for it. abstract class NgModuleRef { |
@mlc-mlapis As @kewde explained here #24962 (comment), yes there is a destroy() method which destroys the Injector (and call the methods ngOnDestroy on all services inside this injector) + call the registered onDestroy callbacks on the NgModuleRef itself. But the ngModule instance (or NgModuleRef) itself is not cleared as there is still a reference to it inside the router inside a LoadedRouterConfig as the module attribute => angular/packages/router/src/config.ts Line 501 in dfb072a
This attribute is never cleared and therefor the NgModuleRef is never gc'ed. On the other hand, the NgComponentOutlet does clear the reference to the NgModule instance :
|
To work around that, I'm currently using an ugly hack : const home = this._router.config.find(route => route.path === `home`);
home.children = this._extensionService.destroyAllExtensions();
this._router.resetConfig(this._router.config);
// [...]
public destroyAllExtensions(): Routes {
// [...]
for (const [extensionId, { instance }] of this._extensions) {
if (instance) {
instance.onDestroy();
}
this._extensionDestroySubject$.next(extensionId); // my internal wiring
}
this._extensions.clear(); // my internal wiring
this._extensionsInitializationListeners.clear(); // my internal wiring
const routes: Routes = [];
// The extension selection screen.
routes.push({
path: ``,
component: this._extensionSelectorComponent,
});
// When the user is not logged in, I register a "Missing Extension" placeholder for this catchall route.
routes.push({
path: `:extensionId`,
component: this._extensionMissingComponent,
});
return routes;
} By resetting the router config, I'm effectively destroying the references to the NgModule instances |
Ok - so if the user is logging out, after the session is cleared, why can't
you force the window to reload? Doesn't that solve the problem?
setTimeout(()={window.location.href=restartLocation;},1)
…On Thu, Jun 10, 2021 at 3:44 AM Eddy ***@***.***> wrote:
@ShamimIslam <https://github.com/ShamimIslam> I globally agree with you
about : "If you need to destroy a lazy loaded module to clear your data /
avoid memory leak, your problem is elsewhere".
But I still want this feature because I have the following use case :
I have a huge "macro app" which assemble different tools for all the
different job in the same company. Each employee is allowed to use some of
the tools. I have a kind of plugin system (based among other thing on lazy
loading via the router) where each tool is a plugin / NgModule and each
time a user log in, based on the user permissions, I'm loading only the
allowed tools. But when the user logout, I specifically need to unload all
the tools because the next user to login might not have the same
permissions and therefor will not load the same tools. In my case, it's not
about clearing data, it's about "clearing / unloading code OR destroying
instances" the next user will not be allowed to use.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#24962 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAXKQTS4MOGPIBOLATK5UOTTSBUMPANCNFSM4FKTMA2A>
.
|
|
Ahh. See, now you've explained the why. So far it's been a "web app" - now
it's a locally packaged electron solution. Got it.
Thanks.
…On Fri, Jun 11, 2021 at 11:14 AM Eddy ***@***.***> wrote:
Ok - so if the user is logging out, after the session is cleared, why
can't you force the window to reload? Doesn't that solve the problem?
setTimeout(()={window.location.href=restartLocation;},1)
@ShamimIslam <https://github.com/ShamimIslam>
- Just because a solution works doesn't mean it is a good solution
- In the context of a webapp, reloading the window means loading and
parsing once again all the init chunks of your app plus lazy loading all
the remaining chunks. And the user has to wait for the app to start again.
Also, you might have some context that you wish to preserve between users
login and logout
- In my context, the Angular is packaged with electron as a "native
desktop app" and launched in full screen. The point is that the users feel
like they're using a native application as much as possible. If I have to
reload the BrowserWindow every time a user logs out, it kind of kills the
native app feeling
- Still in my context, I do indeed need to preserve some context
between users session and I think that saving this context somehow
(localStorage, main process, etc.) is an uglier hack than the one I've
explained earlier
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#24962 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAXKQTTEWJB5GARXXP7YVFTTSIR7HANCNFSM4FKTMA2A>
.
|
Hello everyone. If anyone of you thinks, that router should be able to automatically destroy modules, and therefore services of the route that is being unloaded, please vote for this comment.
This voting is valid until 26.6.2021 |
@Akxe There is probably a slight problem with the correct understanding of the instruction because it looks like voting should be done on the root of such a feature request and not on the bot comment. In this case, it means that all ten votes won't be counted. It is also a general problem in other feature requests, where people vote in the wrong places. |
Let's hope that angular devs will solve this easily understandable mistake people (including myself) made. Ps: Voted for feature too now! |
How can I vote for it? I discovered the issue after the voting ended! |
@Rush This issue is not closed so you can vote right now. |
@Rush Vote for it anyway. Actually vote for this one too. This one is required for the other one anyway. Whit this completed it might be possible to do it ourselves 😊 |
Closing as duplicate of #37095 |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
I'm submitting a...
Current behavior
Currently the lazy loaded module sets itself to the destroyed state. When we revisit the lazy route, it still loads the already-destroyed module. It doesn't unset itself.
Destroying it for a second time results in:
Expected behavior
I expect lazy modules to unload themselves once destroyed, re-initializing themselves when they're needed again.
What is the motivation / use case for changing the behavior?
Our application has to manage multiple users. Our services build up a toxic cache of data that belongs to user Alice. When we switch to user Bob, we call destroy() on the lazy loaded module and reload the module for Bob.
The text was updated successfully, but these errors were encountered: