angular: Router.resetConfig doesn't make child routes navigable

I’m submitting a … (check one with “x”)

[ x ] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

Calling router.resetConfig(newConfig) makes new routes available if they are top-level routes. If the new routes are children of an existing route, they are not navigable, despite being visible in router.config.

Expected behavior

Calling router.resetConfig(newConfig) should make all new routes available, including children of an existing route.

Reproduction of the problem

From within a component, call this.router.resetConfig() with an updated route config – the new routes should be children of an existing route. E.g:

let routerConfig = this.router['config'];
routerConfig[0]._loadedConfig.routes[0].children.push({
  path: `lazy-loaded-module-1`,
  loadChildren: `/lazy-loaded-module-1/app.module.js#ModuleOne`
});
this.router.resetConfig(routerConfig);

(I understand that the above is not strictly standard but conceptually, should still work.)

What is the motivation / use case for changing the behavior?

My Application needs to download additional modules after logging in. These should all be registered as child routes of an ‘/app’ route.

  • /login
  • /app
  • /app/home
  • /app/lazy-loaded-module-1 // Added at runtime
  • /app/lazy-loaded-module-2 // Added at runtime

Please tell us about your environment:

  • Angular version: 2.0.0-rc.5
  • Browser: all (Tested in Chrome 52)
  • Language: TypeScript 1.8.9

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 5
  • Comments: 19 (5 by maintainers)

Most upvoted comments

@m-sawicki: Ah, I think I know the problem that you’ve encountered. The route guard needs to be attached to a route, but if the route doesn’t exist yet, it’s impossible to load. In this case, you must have a route to handle the request – indeed, this may be a wildcard (**) route.

To adjust the earlier example, after you set isLoaded to true, instead of calling resolve(true), you’ll actually call resolve(false). This will stop the wildcard route from loading. Instead, you’ll retry the original request.

// Now on the wildcard route (**)
public canActivate (_, state: RouterStateSnapshot): Promise<boolean> {
  return new Promise(resolve => {
    if (this.isLoaded) {
      // The routes have already been added. If we've hit this again, the route definitely doesn't exist.
      resolve(true);
      return;
    }

    this.http.get('/api/home')
      .subscribe(menuGroups => {
        this.router.resetConfig(this.constructAppRoutes(menuGroups));

        // Set isLoaded to true, stop the original navigation request
        this.isLoaded = true;
        resolve(false);

        // Retry the original navigation request
        this.router.navigateByUrl(state.url);
      });
  });
}

I used @Jonymul 's code but I had a small problem - my browser history had a useless entry with the URL of the root of my app. That means go to the actual previous page I had to click Back twice.

I used this.router.navigateByUrl(state.url, { replaceUrl: true }); instead, where { replaceUrl: true } is the fix for this.

@m-sawicki canActivate() doesn’t have to synchronously return a boolean. Instead, you can return a Promise or an Observable. Below, the Promise always resolves with true, but only after the routes have been added. The HTTP request should probably handle the error case, but I’ll leave that to you.

Hope this helps.

public canActivate (): Promise<boolean> {
  return new Promise(resolve => {
    if (this.isLoaded) {
      // The routes have already been added. Resolve and return to exit out of the function
      resolve(true);
      return;
    }

    this.http.get('/api/home')
      .subscribe(menuGroups => {
        // Reset the config using a newly created route config
        this.router.resetConfig(this.constructAppRoutes(menuGroups));

        // Set isLoaded to true and resolve
        this.isLoaded = true;
        resolve(true);
      });
  });
}

private constructAppRoutes (menuGroups: any): Routes {
  // ...
}

@Jonymul there is also a slightly cleaner way to modify the routeConfig inside a lazy loaded module. Each component is provided its route config with the ActivatedRoute.routeConfig property. You can modify that instead of relying on the _loadedConfig

http://plnkr.co/edit/fBqrEpqafiVSB2Vvapzb?p=preview