Search code examples
angularangular-routingangular-lazyloading

How to properly structure angular app with multiple modules that can access one another?


I am still getting used to Angular and how to properly structure different components and modules. I want to set my app up according to best practices and maximize efficiency. I currently have multiple modules. I have a "base" module that I want to navigate to, but depending on the URL, I want to incorporate components from other modules. Here is how my app-routing.module is currently set up:

const routes: Routes = [
  { path: '', component: BaseComponent },
  { path: 'featureone', loadChildren: () => import('./feature-one/feature-one.module').then(m => m.FeatureOneModule) },
  { path: 'featuretwo', loadChildren: () => import('./feature-two/feature-two.module').then(m => m.FeatureTwoModule) },
  { path: '**', redirectTo: '', pathMatch: 'full'}
];

I understand this routing is not set up properly, but I am not sure how I can properly set this up in the most efficient way.

Currently, if I navigate to '', it will load the BaseComponent as expected. If I add <app-feature-one></app-feature-one> or <app-feature-two></app-feature-two> to the BaseComponent template, it will throw an error like "Cannot access 'FeatureOneModule' before initialization"

Is there some way where I could keep routes such as 'featureone' and 'featuretwo' where it would navigate to the BaseComponent, and I could add logic to display <app-feature-one></app-feature-one> or <app-feature-two></app-feature-two> and only load FeatureOneModule if I navigate to 'featureone' or FeatureTwoModule if I navigate to 'featuretwo'?


Solution

  • With your current configuration, because { path: '', component: BaseComponent }, is first, no matter what url you issue, it will always resolve to BaseComponent. Angular resolves route by performing a DFS search and will stop at the first match, so you'd have to be concise when defining the routes.

    A way to solve this would be do add pathMatch: 'full':

    { path: '', component: BaseComponent, pathMatch: 'full' },
    ...
    

    it will throw an error like "Cannot access 'FeatureOneModule' before initialization"

    You are getting this error because app-feature-one and app-feature-two are components that belong to lazy-loaded modules, so, unless you imperatively import those modules, you won't be able to use them.

    Is there some way where I could keep routes such as 'featureone' and 'featuretwo' ...

    A quick way to solve this would be to use named outlets:

    const routes: Routes = [
      { 
        path: '', component: BaseComponent, pathMatch: 'full',
        children: [
           { path: 'featureone', loadChildren: () => import('./feature-one/feature-one.module').then(m => m.FeatureOneModule), outlet: 'feat-one' },
      { path: 'featuretwo', loadChildren: () => import('./feature-two/feature-two.module').then(m => m.FeatureTwoModule), outlet: 'feat2' },
       ]
    
      },
      { path: '**', redirectTo: '', pathMatch: 'full'}
    ];
    

    then, in your base-component.component.html

    <router-outlet name="feat-one"></router-outlet>
    
    <!-- ... -->
    
    <router-outlet name="feat-two"></router-outlet>
    

    In order to navigate to one of them(or to both at the same time), you'll have to use something like this:

    [routerLink]="[{ outlets: { 'feat-one': 'featureone' } ... }]"