Skip to content

Expose '_loadedConfig' in a public way. #24069

Open

Description

@CarstenLeue

I'm submitting a...


[ ] 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
 

Activity

  1. benelliott commented on May 23, 2018

    @benelliott
    Contributor

    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.

  2. CarstenLeue commented on May 23, 2018

    @CarstenLeue
    Author

    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:

    const routes: Routes = [{
      path: '',
      redirectTo: '/home/',
      pathMatch: 'full'
    },
    {
      path: 'home',
      loadChildren: 'app/my-app/my-app.module#MyAppModule',
      component: PageComponent
    }, {
      path: '**',
      component: PageComponent
    }];

    PageComponent:

    • depending on the current route, make a REST call
    • the response of the REST call returns an identifier of a component
    • map the component to a Type. That type is known to be part of MyAppModule
    • instantiate the component for that type and add it as a child
  3. benelliott commented on May 23, 2018

    @benelliott
    Contributor

    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?

    export class PageComponent implements OnInit {
        constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
    
        ngOnInit() {
            // ...do some logic with this.componentFactoryResolver...
        }
    }
  4. CarstenLeue commented on May 23, 2018

    @CarstenLeue
    Author

    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) {
        // default
        super();
    
        // get the correct factory
        // TODO is there a better way, we use an internal property for now ...
        const fctResolver = _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.

  5. benelliott commented on May 24, 2018

    @benelliott
    Contributor

    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.

  6. CarstenLeue commented on May 24, 2018

    @CarstenLeue
    Author

    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:
    1. I added a route specific wrapper around the original page component
    2. I added that component to the entryComponents section of the lazily loaded module
    3. 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?

  7. benelliott commented on May 24, 2018

    @benelliott
    Contributor

    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.

  8. added this to the needsTriage milestone on May 24, 2018
  9. modified the milestones: needsTriage, on May 24, 2018
  10. jasonaden commented on May 24, 2018

    @jasonaden
    Contributor

    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).

  11. gizm0bill commented on Jul 17, 2018

    @gizm0bill

    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:

    canActivate( route: ActivatedRouteSnapshot ) {
      return route.routerConfig._loadedConfig.module.injector.get(SomeToken).someProp === someValue;
    }
  12. itsdevdom commented on Oct 23, 2018

    @itsdevdom

    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?

  13. 10 remaining items

  14. locked and limited conversation to collaborators on Mar 24, 2020
  15. added
    featureLabel used to distinguish feature request from other issues
    and removed on May 14, 2021
  16. added
    feature: under considerationFeature request for which voting has completed and the request is now under consideration
    on Jun 5, 2021
  17. unlocked this conversation on Jan 5, 2022
  18. removed this from the Ivy (not in use) milestone on Jan 5, 2022
  19. added this to the Backlog milestone on Jan 5, 2022
  20. CFox17 commented on Jul 22, 2023

    @CFox17

    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;
    	}, []);
    }
    
  21. JBBianchi commented on Feb 13, 2024

    @JBBianchi

    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.

  22. intellix commented on Feb 20, 2024

    @intellix

    haven't really seen it said yet but, we're loading ROUTES async via a CMS like so (as recommended in standalone components guide):

    {
      provide: ROUTES,
      useFactory: () => {
        const cmsRouteService = inject(CmsRouteService);
        return [{ path: '', loadChildren: () => cmsRouteService.getRoutes() }];
      },
      multi: true,
    },

    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

  23. Harpush commented on Nov 13, 2024

    @Harpush

    Any official solution here?

  24. shyam0203 commented on Nov 22, 2024

    @shyam0203

    any solution to access _loadedRoutes (in angular 17) for lazyLoaded modules ?

  25. muuvmuuv commented on May 8, 2025

    @muuvmuuv

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: routerfeatureLabel used to distinguish feature request from other issuesfeature: under considerationFeature request for which voting has completed and the request is now under considerationrouter: lazy loading

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions