AngularJSChild & Nested Routes

Child and Nested Routes

Nested routes (also called child routes) let you build hierarchical UI structures that match your URL hierarchy. A parent route renders a layout shell (header, sidebar, tabs) and an inner <router-outlet> displays the active child route's content.

This is the standard pattern for feature areas: /users → user list, /users/42 → user profile, /users/42/settings → user settings — all within a shared layout.

Defining Child Routes

TS
// app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'users',
    component: UsersShellComponent,   // renders the layout + <router-outlet>
    children: [
      { path: '',           component: UserListComponent },    // /users
      { path: ':id',        component: UserDetailComponent },  // /users/42
      { path: ':id/edit',   component: UserEditComponent },    // /users/42/edit
      { path: ':id/posts',  component: UserPostsComponent },   // /users/42/posts
    ],
  },
  { path: '', redirectTo: '/users', pathMatch: 'full' },
];
The Parent Layout Shell

The parent component (UsersShellComponent) renders the shared chrome and a <router-outlet> where child components appear.

TS
// users-shell.component.ts
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';

@Component({
  standalone: true,
  imports: [RouterOutlet, RouterLink, RouterLinkActive],
  template: `
    <div class="users-layout">
      <aside>
        <nav>
          <a routerLink="/users" routerLinkActive="active"
             [routerLinkActiveOptions]="{ exact: true }">All Users</a>
        </nav>
      </aside>

      <main>
        <!-- Child route components render here -->
        <router-outlet />
      </main>
    </div>
  `,
})
export class UsersShellComponent {}
Deeper Nesting (Three Levels)

TS
export const routes: Routes = [
  {
    path: 'org',
    component: OrgShellComponent,          // /org — org layout
    children: [
      { path: '', component: OrgHomeComponent },
      {
        path: ':orgId',
        component: OrgDetailShellComponent, // /org/acme — org detail layout
        children: [
          { path: '',         component: OrgOverviewComponent },
          { path: 'members',  component: OrgMembersComponent },
          {
            path: 'projects',
            component: ProjectsShellComponent,  // /org/acme/projects — projects layout
            children: [
              { path: '',    component: ProjectListComponent },
              { path: ':id', component: ProjectDetailComponent },
            ],
          },
        ],
      },
    ],
  },
];
Note
Each level that uses children must have a <router-outlet> in its template. Without it, child components have nowhere to render and will be silently ignored.
Component-Less Parent Routes

Sometimes you want to group routes and apply shared guards or resolvers without an actual shell component. Use a component-less route with just a path and children.

TS
import { authGuard } from './guards/auth.guard';

export const routes: Routes = [
  {
    path: 'admin',
    // No component — just provides a path prefix and shared guard
    canActivate: [authGuard],
    children: [
      { path: '',       component: AdminDashboardComponent },
      { path: 'users',  component: AdminUsersComponent },
      { path: 'config', component: AdminConfigComponent },
    ],
  },
];
Lazy-Loaded Nested Routes

Feature areas are commonly lazy-loaded. The parent route uses loadChildren pointing to a file that exports the child routes array.

TS
// app.routes.ts
export const routes: Routes = [
  {
    path: 'products',
    loadChildren: () =>
      import('./products/products.routes').then(m => m.PRODUCT_ROUTES),
  },
];

// products/products.routes.ts
import { Routes } from '@angular/router';

export const PRODUCT_ROUTES: Routes = [
  {
    path: '',
    component: ProductsShellComponent,
    children: [
      { path: '',    component: ProductListComponent },
      { path: ':id', component: ProductDetailComponent },
      {
        path: ':id/reviews',
        loadComponent: () =>
          import('./product-reviews/product-reviews.component')
            .then(m => m.ProductReviewsComponent),
      },
    ],
  },
];
Navigating to Child Routes

HTML
<!-- Absolute path -->
<a routerLink="/users/42">User 42</a>
<a routerLink="/users/42/edit">Edit User</a>

<!-- From inside a child component — relative navigation -->
<a routerLink="..">Back to list</a>        <!-- parent -->
<a routerLink="../43">Next user</a>         <!-- sibling param -->
<a routerLink="edit">Edit</a>              <!-- child of current -->

TS
import { Component, inject } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';

@Component({ standalone: true, template: `...` })
export class UserDetailComponent {
  private router = inject(Router);
  private route  = inject(ActivatedRoute);

  goToEdit(id: number) {
    // Absolute
    this.router.navigate(['/users', id, 'edit']);

    // Relative to current route
    this.router.navigate(['edit'], { relativeTo: this.route });

    // Up one level then into a sibling
    this.router.navigate(['..', id + 1], { relativeTo: this.route });
  }
}
Inheriting Parent Parameters

By default, a child component can only see parameters defined in its own route segment. To access parent parameters from a child, enable parameter inheritance.

TS
// Enable param inheritance in router setup
import { provideRouter, withRouterConfig } from '@angular/router';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes, withRouterConfig({ paramsInheritanceStrategy: 'always' })),
  ],
});

// Now a child can read parent params via its own ActivatedRoute
// Route: /users/:userId/posts/:postId
// In PostDetailComponent:
const userId = route.snapshot.paramMap.get('userId');  // from parent :userId
const postId = route.snapshot.paramMap.get('postId');  // from own :postId
Tip
Without paramsInheritanceStrategy: 'always', access parent params via route.parent?.snapshot.paramMap.get('userId').
Resolvers with Child Routes

Resolvers pre-fetch data before a route activates. They work seamlessly with child routes.

TS
// user.resolver.ts
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { UserService } from './user.service';
import { User } from './user.model';

export const userResolver: ResolveFn<User> = (route) => {
  const id = route.paramMap.get('id')!;
  return inject(UserService).getUser(+id);
};

// app.routes.ts — resolve before any child activates
{
  path: 'users/:id',
  component: UserDetailShellComponent,
  resolve: { user: userResolver },
  children: [
    { path: '', component: UserOverviewComponent },
    { path: 'posts', component: UserPostsComponent },
  ],
}

TS
// UserDetailShellComponent — read resolved data
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({ standalone: true, template: `...` })
export class UserDetailShellComponent {
  private route = inject(ActivatedRoute);

  user = this.route.snapshot.data['user'];  // pre-fetched before component renders
}
Named Outlets in Nested Routes

A component can have both a primary and a named <router-outlet>. The named outlet renders a side panel or modal independently.

TS
// template with named outlet
// <router-outlet></router-outlet>           ← primary
// <router-outlet name="detail"></router-outlet>  ← named

const routes: Routes = [
  {
    path: 'products',
    component: ProductsPageComponent,
    children: [
      { path: '', component: ProductListComponent },
      {
        path: ':id',
        component: ProductPreviewComponent,
        outlet: 'detail',   // renders in the named outlet
      },
    ],
  },
];

// Navigate to populate the named outlet
router.navigate([
  '/products',
  { outlets: { detail: [selectedProductId] } },
]);
Common Patterns Summary

Pattern

How to implement

Layout with tabs

Parent = tab shell, children = tab pages

Master-detail

Parent = list, child = detail panel

Wizard / stepper

Parent = wizard shell, children = steps

Protected section

Component-less parent with canActivate guard

Feature area

loadChildren pointing to feature routes file

Pre-fetch data

resolve on the parent, read from children via route.parent

Warning
Do not forget to add <router-outlet /> to every parent component that defines child routes. It is the single most common cause of child routes appearing to do nothing.

Summary: Nest routes using the children array in a route config. The parent component renders the shared layout and a <router-outlet /> where child components appear. Use component-less parents for grouping and shared guards. Combine with lazy loading (loadChildren) for optimal bundle splitting.