Child routes and lazy loading
A child route shows a component inside another page: on a post page the comments open under the text at the address /posts/1/comments. Lazy loading means a page's code is downloaded only on the first visit to it, so the application opens faster.
Child routes
First add a comments method and a PostComment type to PostApi (import it into the service from ./post-comment):
// src/app/post-api.ts
getComments(postId: number) {
return this.http.get<PostComment[]>(`${this.url}/${postId}/comments`);
}// src/app/post-comment.ts
export interface PostComment {
id: number;
postId: number;
name: string;
email: string;
body: string;
}Create the component with ng generate component post-comments and describe it as a child route in the children property:
// src/app/app.routes.ts
export const routes: Routes = [
{ path: 'posts', component: PostList },
{
path: 'posts/:id',
component: PostDetail,
children: [{ path: 'comments', component: PostComments }],
},
];A second router-outlet
The child component is rendered in a <router-outlet /> inside the parent. Add one to the post template together with a link:
<!-- src/app/post-detail/post-detail.html -->
@if (post(); as p) {
<h2>{{ p.title }}</h2>
<p>{{ p.body }}</p>
<a routerLink="comments">Show comments</a>
<router-outlet />
}Add RouterOutlet to the imports of PostDetail. An address without a leading slash in routerLink is relative to the current page: on /posts/1 the link leads to /posts/1/comments.
The parent's parameter
The id parameter belongs to the parent route, so the child component takes it from parent:
// src/app/post-comments/post-comments.ts
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { PostApi } from '../post-api';
@Component({
selector: 'app-post-comments',
template: `
@for (c of comments(); track c.id) {
<p><b>{{ c.email }}</b>: {{ c.body }}</p>
}
`,
})
export class PostComments {
private id = Number(inject(ActivatedRoute).parent?.snapshot.paramMap.get('id'));
comments = toSignal(inject(PostApi).getComments(this.id), { initialValue: [] });
}Click "Show comments" — five comments appear under the post, and the address becomes /posts/1/comments.
Lazy loading a component
Instead of component, give loadComponent — a function with a dynamic import():
// src/app/app.routes.ts
{
path: 'todos',
loadComponent: () => import('./todo-list/todo-list').then((m) => m.TodoList),
},Remove the regular import of TodoList at the top of the file, otherwise it still ends up in the main bundle. ng serve shows a separate Lazy chunk files block in the terminal — that is the page code that is downloaded when you go to /todos.
A lazy group of routes
A whole section with several pages is moved into its own routes file and connected with loadChildren:
// src/app/admin/admin.routes.ts
import { Routes } from '@angular/router';
import { Dashboard } from './dashboard/dashboard';
export const adminRoutes: Routes = [
{ path: '', component: Dashboard },
];// src/app/app.routes.ts
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then((m) => m.adminRoutes),
},All pages of the /admin section are loaded as one chunk on the first visit.
Common mistake
Forgetting <router-outlet /> in the parent's template. The address changes to /posts/1/comments, there are no errors, but the comments do not appear — the child component has nowhere to render. If a route has children, its component needs its own router-outlet.