Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Routing in Angular

This maybe a common question and if there are better answers please point me towards it. That being said, here's the issue:

On the top level, the angular app I am developing exposes a login path and paths to 4 separate dashboards depending on who logs in. This part is working as expected.

For each dashboard, I have a side navigation that is more or less same (some options are only shown for certain types of user). In the dashboard, I have a nested router outlet. Whenever I try to load a module inside the nested outlet, Angular can't match the path. Here's what I have so far:

app-routing.module.ts

const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'login' },
  { path: 'login', loadChildren: () => import('./modules/auth/auth.module').then(m => m.AuthModule) },
  //{ path: 'dashboard', loadChildren: () => import('./modules/dashboard/dashboard.module').then(m => m.DashboardModule) }
  { path: 'admin', loadChildren: () => import('./modules/admin/admin.module').then(m => m.AdminModule) },
];

admin-routing.module.ts


const routes: Routes = [
  { path: '', pathMatch: 'full', component: AdminComponent, children: [
    { path: 'employee', loadChildren: () => import('./../employee/employee.module').then(m => m.EmployeeModule) },
    { path: 'attendance', loadChildren: () => import('./../attendance/attendance.module').then(m => m.AttendanceModule) },
    { path: 'customer', loadChildren: () => import('./../customer/customer.module').then(m => m.CustomerModule) },
    { path: 'order', loadChildren: () => import('./../order/order.module').then(m => m.OrderModule) },
    { path: 'expense', loadChildren: () => import('./../expense/expense.module').then(m => m.ExpenseModule) },
  ]},
];

app.component.html

<router-outlet></router-outlet>

admin.component.html

<mat-drawer-container mode="side" id="dashboard-drawer-container" hasBackdrop="false">
  <mat-drawer #drawer id="sidenav-container">
    <app-sidenav></app-sidenav>
  </mat-drawer>
  <div id="dashboard-container">
    <router-outlet></router-outlet>
  </div>
</mat-drawer-container>

Now the expected behavior is as follows:

  1. When navigated to /admin, the AdminComponent will be rendered and the sidenav will be visible
  2. When a link is clicked on the sidenav, the content should be rendered in the nested router in the admin component (for example, admin/employee)
  3. When other routes are accessed inside the module loaded in (2), it should be rendered inside an outlet in that module (for example, admin/employee/:id) for employee detail page where, employee module has a nested router

I tried with named outlets and it kept throwing error. If I move the children out of admin routes and make them independent routes, it kind of works but, the content is rendered on the outermost (app) router outlet and the sidenav is not rendered.

Any help or suggestion will be greatly appreciated.

like image 471
Abrar Hossain Avatar asked Jul 11 '20 14:07

Abrar Hossain


People also ask

What is nested routing?

Nested Routes are a powerful feature. While most people think React Router only routes a user from page to page, it also allows one to exchange specific fragments of the view based on the current route.

Can we have multiple routes in Angular?

Angular Router supports multiple outlets in the same application. A component has one associated primary route and can have auxiliary routes. Auxiliary routes enable developers to navigate multiple routes at the same time.

What is child routes in Angular?

With child routes, you can have a component-like structure defined for the routes in your app. It is critical as there are views that the user should not be able to access unless they are in a particular view. This way, the structure becomes tree-like, just like the structure of components.


Video Answer


1 Answers

Let's break the problem into a small one:

app.module.ts

const routes: Routes = [
  {
    path: '',
    // pathMatch: 'full',
    children: [
      {
        path: 'foo',
        component: FooComponent
      },
    ],
  },
  {
    path: '**',
    component: NotFoundComponent,
  }
];

app.component.html

<router-outlet></router-outlet>

<button routerLink="/foo">go to foo</button>

ng-run demo.


If we click on the button, the Angular Router will schedule a route transition. This involves a quite interesting process which is composed of multiple phases.

One of these phases is the one called Apply Redirects and it's where the redirects are resolved and where NoMatch errors come from. This is also where we can find more about pathMatch: 'full'.
In this phase it will go through each configuration object and will try to find the first one that matches with the issued url(e.g /foo).

It will first encounter this one(it happens in matchSegmentAgainstRoute):

{
  path: '',
  // pathMatch: 'full',
  children: [
    {
      path: 'foo',
      component: FooComponent
    },
  ],
},

then, the match function will be invoked:

const {matched, consumedSegments, lastChild} = match(rawSegmentGroup, route, segments);

here, we stop at route.path === '':

if (route.path === '') {
  if ((route.pathMatch === 'full') && (segmentGroup.hasChildren() || segments.length > 0)) {
    return {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}};
  }

  return {matched: true, consumedSegments: [], lastChild: 0, positionalParamSegments: {}};
}

So, here is one case where pathMatch option makes the difference. With the current configuration(pathMatch not set),

return {matched: true, consumedSegments: [], lastChild: 0, positionalParamSegments: {}};

will be reached and then it will proceed to go through the children array. So, in this case, the FooComponent will successfully be displayed.

But, if we have pathMatch: 'full', then the expression

if ((route.pathMatch === 'full') && (segmentGroup.hasChildren() || segments.length > 0)) { }

will be true, because segments.length > 0, in this case segments is ['foo']. So, we'd get matched: false, which means the FooComponent won't appear in the view.

like image 129
Andrei Gătej Avatar answered Sep 29 '22 22:09

Andrei Gătej