You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[ ] Performance issue
[x ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
[ ] Other... Please describe:
Current behavior
From a component that is loaded at bootstrap time I would like to dynamically instantiate a component that is loaded lazily on the current route. For that purpose I need access to the ComponentFactoryResolver for that route.
The only way I could find to access that resolver is via the private "_loadedConfig" property on the active route.
Expected behavior
I would like to be able to access the resolved via a public API, not relying on an implementation detail.
What is the motivation / use case for changing the behavior?
Better stability of my code by relying on public APIs, only.
Environment
Angular version: 6.0.2
Browser:
- [x ] Chrome (desktop) version XX
- [x] Chrome (Android) version XX
- [x] Chrome (iOS) version XX
- [x] Firefox version XX
- [x] Safari (desktop) version XX
- [x] Safari (iOS) version XX
- [x] IE version XX
- [x] Edge version XX
I have a generic component that dynamically instantiates another component based on configuration returned via a REST request. The generic component is loaded at bootstrap time, the dynamic component is part of the bundle loaded via a lazy route.
In that case, why not just inject ComponentFactoryResolver in the constructor of your PageComponent rather than trying to get it from a private property on the route?
exportclassPageComponentimplementsOnInit{constructor(privatecomponentFactoryResolver: ComponentFactoryResolver){}ngOnInit(){// ...do some logic with this.componentFactoryResolver...}}
Because that turns out to be the wrong one, that instance does not find the dynamically loaded components.
The constructor of PageComponent actually looks like this:
constructor(aFctResolver: ComponentFactoryResolver,aActivatedRoute: ActivatedRoute){// defaultsuper();// get the correct factory// TODO is there a better way, we use an internal property for now ...constfctResolver=_getComponentFactoryResolver(aActivatedRoute.routeConfig)||aFctResolver;
I had to add _getComponentFactoryResolver - which uses the private property - to get the correct resolver. The injected one does not contain the dynamically loaded components.
Note that PageComponent is part of an NgModule which is imported both into the application module as well as into the dynamically loaded module.
Could you create a thin component in the lazy-loaded module, much like PageComponent, but which injects the ComponentFactoryResolver for its own module (and thus can resolve the other lazy-loaded components), then delegate to this new component for dynamically instantiating the component instead?
More generally, why can you not just write a route configuration for the lazy-loaded module that will instantiate the correct component, and then instead of instantiating a component after the REST call, just redirect to the correct route? Maybe the REST call could return the route instead of the component identifier.
Answer to the second question, first:
The different components are not supposed to be addressed via different routes. It is a business requirement that the routes need to stay stable, even though the REST call could return different mapping information. That's why I cannot do a redirect.
Thin wrapper:
This would in principle be possible. So far I am not using this approach, because:
using a common component (PageComponent) requires less code and less coding effort when configuring new, lazily loaded routes. That is however not a showstopper.
the main reason is that this approach did not work, but that might be due to a lack of expertise on my side. What I tried, was this:
I added a route specific wrapper around the original page component
I added that component to the entryComponents section of the lazily loaded module
I used that component as "component" to the router for my lazy route
This did not work, the development build failed, saying that the wrapper component was unknown.
Do you have an example how the router config would have to look like?
The crux of your problem is that the ComponentFactoryResolver injected into the main (eagerly-loaded) module is not the same as the one you would find in the lazy-loaded module. The "thin wrapper" solution I am describing solves this by delegating the component instantiation to a fairly trivial component that lives inside the lazy-loaded module.
I would expect it to work like this:
Some service (SomeService) resides in your main module and fires off the REST call
It saves the result of the call and uses it to reroute to the correct lazy-loaded module (you will have a mapping from REST response => lazy-loaded module route)
The lazy-loaded module has a default empty route to a ComponentInstantiatorComponent which is defined inside the lazy-loaded module
When the lazy-loaded module is navigated to, it initialises the ComponentInstantiatorComponent, which injects both the ComponentFactoryResolver and SomeService, and uses the saved REST call result held by SomeService to instantiate the correct component with ComponentFactoryResolver
In terms of requiring less code, you can define ComponentInstantiator in a LazySharedModule which is imported into all of your lazy-loaded modules, so you only have to define it once.
I still maintain however that it seems a very roundabout way of doing things, as you are essentially re-implementing the Router - maybe you should reassess the design a little bit.
Thanks for the issue. With the new rendering layer, this problem actually goes away because ComponentFactoryResolver will not be needed anymore. I've marked it as such, and for now it'll be better to use an interim solution (such as what you're currently doing getting from the private property).
How can I achieve a CanActivate guard depending on a value of an injectable defined by the currently trying to activate lazy loaded route module without _loadedConfig?
Basically something like:
We have the use case that we dynamically (meaning at runtime) generate routes using a given configuration. Without lazy-loading, things are simple - one can manipulate the global router configuration object to add / remove routes. With lazy-loaded routes, however, we need access to the lazy-loaded route information - which is only available via the private property _loadedConfig.
Obviously, using non-public API is not ideal. Was there a reason to not expose this property to the public? Is this also a use case that will be fixed by Ivy?
Reacted by Yarn, David, Florian Neumann, zalmane and teeeemoji
Hi I am simply trying to get all subroutes for a given already loaded lazy-loaded module so I can just filter on "data.name" attribute to get a particular path out of a Routes object so I don't need to have hardcoded paths everywhere in my components when doing this.router.navigate() and so that I can have everything defined in Routes variables. That way I can change the URL of something by just changing it in one place!
But I can't find a way to do this without using internal property _loadedConfig
let allLoadedRoutesInApplication = this.flattenRoutes(this.router.config)
console.log("allLoadedRoutesInApplication: ",allLoadedRoutesInApplication )
// get all the children of a route including children's children and so on, and put them all into 1 flat list
flattenRoutes(myRoutes: any[], startingPath = '/') {
return myRoutes.reduce((acc: any[], route: any) => {
// push the route itself
if(acc.filter(val => val.path.length > 0).map(val => val.path).indexOf(route.path) == -1 && route.data != null) {
route.data.fullPath = startingPath + route.path
acc.push(route)
}
// Why is there no non-internal way to get the already loaded lazy-load module routes ???
if (route._loadedConfig && route._loadedConfig.routes.length > 0) {
const loadedConfigRoutes = route._loadedConfig.routes;
startingPath = startingPath + route.path + "/"
const loadedConfigChildrenList = this.flattenRoutes(loadedConfigRoutes, startingPath);
// push each item in loadedConfigChildrenList now that we've recursively fetched all this routes loaded lazy-load children above
loadedConfigChildrenList.forEach(element => {
if(acc.filter(val => val.path.length > 0).map(val => val.path).indexOf(element.path) == -1 && element.data != null) {
acc.push(element)
}
});
}
if (route.children != null && route.children.length > 0) {
const childrenList = this.flattenRoutes(route.children, startingPath);
// push each item in childrenList now that we've recursively fetched all this routes children above
childrenList.forEach(element => {
if(acc.filter(val => val.path.length > 0).map(val => val.path).indexOf(element.path) == -1 && element.data != null) {
acc.push(element)
}
});
}
return acc;
}, []);
}
If anyone else faceing the same issue, after updating from v13 to v17, I noticed route._loadedConfig doesn't exist anymore. Alternatives are route._loadedRoutes and route._loadedInjector.
Those routes that are loaded from the CMS have a name like "HOME" and other metadata. We'd like to read this list of dynamically added ROUTES so we can get the path by name like "HOME".
There's currently no way as far as I can tell, to get this list of dynamically loaded routes. The RouteConfigLoadEnd appears with 1x route and if I log it, I can see a private _loadedRoutes property that's inaccessible. You can provide routes async, but you can't subscribe to route config changes annoyingly.
In my example I can't have a ReplaySubject with all of the routes in another service that depends on Router because it creates a circular: Router -> CmsRouteService -> MyRouteService -> Router.
All I want is a Router.config$ so I can subscribe to changes and react to them
I built a lib for menu's declared on any route. Maybe this approach solves someones issue here aswell. Still looking for Angular making the API public, see #61192.
Activity
benelliott commentedon May 23, 2018
Can you elaborate a bit on what you are trying to achieve? It's likely that your use case is already covered using a different technique.
CarstenLeue commentedon May 23, 2018
I have a generic component that dynamically instantiates another component based on configuration returned via a REST request. The generic component is loaded at bootstrap time, the dynamic component is part of the bundle loaded via a lazy route.
Like so:
Router:
PageComponent:
benelliott commentedon May 23, 2018
In that case, why not just inject
ComponentFactoryResolverin the constructor of yourPageComponentrather than trying to get it from a private property on the route?CarstenLeue commentedon May 23, 2018
Because that turns out to be the wrong one, that instance does not find the dynamically loaded components.
The constructor of PageComponent actually looks like this:
I had to add
_getComponentFactoryResolver- which uses the private property - to get the correct resolver. The injected one does not contain the dynamically loaded components.Note that
PageComponentis part of an NgModule which is imported both into the application module as well as into the dynamically loaded module.benelliott commentedon May 24, 2018
Could you create a thin component in the lazy-loaded module, much like
PageComponent, but which injects theComponentFactoryResolverfor its own module (and thus can resolve the other lazy-loaded components), then delegate to this new component for dynamically instantiating the component instead?More generally, why can you not just write a route configuration for the lazy-loaded module that will instantiate the correct component, and then instead of instantiating a component after the REST call, just redirect to the correct route? Maybe the REST call could return the route instead of the component identifier.
CarstenLeue commentedon May 24, 2018
Answer to the second question, first:
The different components are not supposed to be addressed via different routes. It is a business requirement that the routes need to stay stable, even though the REST call could return different mapping information. That's why I cannot do a redirect.
Thin wrapper:
This would in principle be possible. So far I am not using this approach, because:
This did not work, the development build failed, saying that the wrapper component was unknown.
Do you have an example how the router config would have to look like?
benelliott commentedon May 24, 2018
The crux of your problem is that the
ComponentFactoryResolverinjected into the main (eagerly-loaded) module is not the same as the one you would find in the lazy-loaded module. The "thin wrapper" solution I am describing solves this by delegating the component instantiation to a fairly trivial component that lives inside the lazy-loaded module.I would expect it to work like this:
SomeService) resides in your main module and fires off the REST callComponentInstantiatorComponentwhich is defined inside the lazy-loaded moduleComponentInstantiatorComponent, which injects both theComponentFactoryResolverandSomeService, and uses the saved REST call result held bySomeServiceto instantiate the correct component withComponentFactoryResolverIn terms of requiring less code, you can define
ComponentInstantiatorin aLazySharedModulewhich is imported into all of your lazy-loaded modules, so you only have to define it once.I still maintain however that it seems a very roundabout way of doing things, as you are essentially re-implementing the
Router- maybe you should reassess the design a little bit.jasonaden commentedon May 24, 2018
Thanks for the issue. With the new rendering layer, this problem actually goes away because
ComponentFactoryResolverwill not be needed anymore. I've marked it as such, and for now it'll be better to use an interim solution (such as what you're currently doing getting from the private property).gizm0bill commentedon Jul 17, 2018
How can I achieve a
CanActivateguard depending on a value of an injectable defined by the currently trying to activate lazy loaded route module without_loadedConfig?Basically something like:
itsdevdom commentedon Oct 23, 2018
We have the use case that we dynamically (meaning at runtime) generate routes using a given configuration. Without lazy-loading, things are simple - one can manipulate the global router configuration object to add / remove routes. With lazy-loaded routes, however, we need access to the lazy-loaded route information - which is only available via the private property
_loadedConfig.Obviously, using non-public API is not ideal. Was there a reason to not expose this property to the public? Is this also a use case that will be fixed by Ivy?
10 remaining items
CFox17 commentedon Jul 22, 2023
Hi I am simply trying to get all subroutes for a given already loaded lazy-loaded module so I can just filter on "data.name" attribute to get a particular path out of a Routes object so I don't need to have hardcoded paths everywhere in my components when doing this.router.navigate() and so that I can have everything defined in Routes variables. That way I can change the URL of something by just changing it in one place!
But I can't find a way to do this without using internal property _loadedConfig
JBBianchi commentedon Feb 13, 2024
If anyone else faceing the same issue, after updating from v13 to v17, I noticed
route._loadedConfigdoesn't exist anymore. Alternatives areroute._loadedRoutesandroute._loadedInjector.intellix commentedon Feb 20, 2024
haven't really seen it said yet but, we're loading ROUTES async via a CMS like so (as recommended in standalone components guide):
Those routes that are loaded from the CMS have a name like "HOME" and other metadata. We'd like to read this list of dynamically added ROUTES so we can get the path by name like "HOME".
There's currently no way as far as I can tell, to get this list of dynamically loaded routes. The RouteConfigLoadEnd appears with 1x route and if I log it, I can see a private _loadedRoutes property that's inaccessible. You can provide routes async, but you can't subscribe to route config changes annoyingly.
In my example I can't have a ReplaySubject with all of the routes in another service that depends on Router because it creates a circular: Router -> CmsRouteService -> MyRouteService -> Router.
All I want is a Router.config$ so I can subscribe to changes and react to them
Harpush commentedon Nov 13, 2024
Any official solution here?
shyam0203 commentedon Nov 22, 2024
any solution to access _loadedRoutes (in angular 17) for lazyLoaded modules ?
muuvmuuv commentedon May 8, 2025
Came across the same issue, my workaround is this https://github.com/muuvmuuv/angular-router-menus/blob/main/projects/angular-router-menus/src/lib/builder.ts#L19 .
I built a lib for menu's declared on any route. Maybe this approach solves someones issue here aswell. Still looking for Angular making the API public, see #61192.