Search code examples
angularangular2-routingangular2-router3

How is routing with nested modules supposed to work in Angular 2?


I can't for the love of cookies figure out how to configure the most recent incarnation of the Angular 2 router (the one from the final release). I have a rather large Angular 2 RC5 app up and running, but need to migrate to the final release now before I can move on.

In this app I have a lot of nested components that, according to Google's docs, should be organised as features in module. The typical URL in my app looks like so

user/34/section/95/client/13/<feature>

but there are also a few others, such as

user/34/settings

so in this case, settings would be a feature module.

I've read the docs, which are rather long-winded and are in no hurry to cut to the chase, only to find out that this case isn't covered at all. I can't believe that this scenario isn't going to work, it would seem to be a huge oversight so I suppose the problem is that I haven't quite grasped how this router actually works - and I have a feeling that I'm not the only one.

I've created this plunker to illustrate my point.

In this plunker I got 2 levels of nesting

app -> benutzer (user) -> klient (client)

just enough to make the issue appear. The expected outcome is that I can navigate to

benutzer/78/klient/56

i.e. the router actually finds that route, which at the moment it doesn't.

Before I moved the code to plunker and added dashboard.component (because I can't easily modify the URL in plunker, so I have to resort to routerLink) I could actually navigate to

klient/56

which shouldn't be possible. It seems that each route defined in another module is added to the root instead of building on top of each other.

Any help with this is greatly appreciated.


Solution

  • Look at all your routes.

    appRoutes = [      // AppModule
      { path: '', redirectTo: 'dashaboard' },
      { path: 'dashboard', component: DashboardComponent
    ];
    benutzerRoutes = [ // BenutzerModule
      { path: 'benutzer/:id', component BenutzerComponent }
    ];
    klientRoutes = [   // KlinetModule
      { path: 'klient/:id', component: KlientComponent }
    ]
    

    The way you import your modules doesn't have any effect on how the routes are constructed. How they are constructed are simply based on how we construct the. What you are expecting with this

    AppModule
       imports -> BenutzerModule
                       imports -> KlinetModule
    

    leading to the following routes

    dashboard/benutzer/:id/klient/:id
    

    is just not the case. Each one of those route arrays are simply added to the root. That's why you can access klient/:id and not dashboard/benutzer/:id.

    I've read the complete documentation for routing a few times, and there aren't any example of nested routes in different modules. All the examples had modules that were loaded from the root route, as your example does, or having nested routes being part of the same route configuration. So since there are no example I guess we need to just work with what we know and decide for ourselves what's the best way.

    There are a couple ways I can think of. The first, most obvious but IMO more intrusive than the next option, is to just add the full routes to the paths

    appRoutes = [      // AppModule
      { path: '', redirectTo: 'dashaboard' },
      { path: 'dashboard', component: DashboardComponent
    ];
    benutzerRoutes = [ // BenutzerModule
      { path: 'dashboard/benutzer/:id', component BenutzerComponent }
    ];
    klientRoutes = [   // KlinetModule
      { path: 'dashboard/benutzer/:id/klient/:id', component: KlientComponent }
    ]
    

    I say this option is more intrusive, as it forces the children to know about the parent. In Angular 2 architecture that is counter to how we structure our components. Parents should know about children, but not necessarily vice versa.

    The other option I can think of is to use loadChildren to load the child modules lazily. I say "lazily" because I can't figure out how to do this eagerly, and not sure if it is even possible to do so. To load the children lazily, we can do

    export const appRoutes: Routes = [
      { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
      { path: 'dashboard', component: DashboardComponent },
      {
        path: 'dashboard/benutzer',
        loadChildren: 'app/benutzer/benutzer.module#BenutzerModule'
      },
      { path: '**', component: NotFoundComponent }
    ];
    export const benutzerRoutes: Routes = [
      { path: ':id', component: BenutzerComponent },
      {
        path: ':id/klient',
        loadChildren: 'app/klienten/klienten.module#KlientenModule'
      }
    ];
    export const klientenRoutes: Routes = [
      { path: ':id', component: KlientComponent }
    ];
    

    In this case we would remove the all the child module imports from their parent @NgModule. This allows for lazy loading of the module. If we leave then, then the module will be loaded eagerly on startup, but not have the desired effect (hence why I said I am not how to do this eagerly).

    Also notice the loadChildren. In the above example I use app as the root. The only reason is that I tested in local environment. I am not a big fan of Plunker. For your Plunker though, you should use src as the root.

    IMO, the lazy loading looks cleaner, as child doesn't know about parent, but this forces you to lazily load the modules, which may not be desired. In some cases though, it is desired, as it allows for a lighter initial load.

    For more on lazy loading, see the docs routing section on Asynchronous Routing