Angular course · Module 4: Routing
Nested Routes and Resolve - Rooms Inside the Dojo
In this lesson2
A dojo is not one hall - it is a building with rooms inside rooms: a training hall, a weapons corner within it, and inside that the profile of one particular samurai. Angular routes nest exactly the same way. In this lesson you will learn two things: how to build routes inside routes (a parent and its children) and how to use a resolver to fetch data before the screen ever appears.
Child Routes - Paths Inside Paths
A route can carry a children property - an array of routes that render inside the parent. There is no nested property in a route definition, there is no RouterChildModule to import, and you do not need a separate routes file for every level: one array, nested as deep as the building goes. Look at how /dojo holds the training hall, and how a samurai profile has tabs of its own.
export const routes: Routes = [
{
path: 'dojo',
component: DojoLayoutComponent,
children: [
{ path: '', component: DojoHomeComponent },
{ path: 'training', component: TrainingComponent },
{
path: 'samurai/:id',
component: SamuraiProfileComponent,
children: [
{ path: '', redirectTo: 'info', pathMatch: 'full' },
{ path: 'info', component: SamuraiInfoComponent },
{ path: 'skills', component: SamuraiSkillsComponent }
]
}
]
}
];Read it like a floor plan: /dojo/training is the training hall inside the dojo, and /dojo/samurai/5/skills is the skills tab in the profile of samurai number 5. A weapons corner would simply be one more entry in the same array. Notice that child paths are relative to the parent - you write training, not /dojo/training, and Angular joins the segments for you. Two more tricks: the empty path { path: '' } is the parent's default view (what to show when nobody has picked a child yet), and redirectTo: 'info' sends anyone who lands on the bare profile straight to the info tab, so the screen is never blank. Read the parent entry top to bottom and you have its shape: the opening brace, then path, then component, then children, then the closing brace.
For the children to have somewhere to appear, the parent needs its own router-outlet:
@Component({
template: `
<header>
<h1>Dojo</h1>
<nav><a routerLink="training">Training</a></nav>
</header>
<router-outlet></router-outlet>
`
})
export class DojoLayoutComponent { }This is the heart of nesting: DojoLayoutComponent draws the shared frame (header, navigation) and <router-outlet> is the window where the active child appears - and it sits in the parent component's own template. Angular has no <child-outlet> element, and the children do not land directly in app.component - that top-level outlet is where DojoLayoutComponent itself was rendered. Go to /dojo/training and TrainingComponent fills the window while the dojo header stays exactly where it was. Every level of nesting has its own outlet, like a room inside a room.
Resolver - Data Before You Enter
By default the component loads at once and you pull the data in ngOnInit - so for a moment the screen sits empty. A resolver reverses that order: it fetches the data before the route activates, and the component has the full set from its very first frame.
import { ResolveFn } from '@angular/router';
export const samuraiResolver: ResolveFn<Samurai> = (route) => {
const service = inject(SamuraiService);
const id = route.paramMap.get('id')!;
return service.getById(+id);
};
// In the route definition
{
path: 'samurai/:id',
component: SamuraiDetailComponent,
resolve: { samurai: samuraiResolver }
}A resolver is a function that returns the data for a route - here it fetches a samurai by the id read from the address. Its type is ResolveFn<T>, the modern functional form and the one to reach for in Angular 19; the old shape was a class implementing the Resolve interface, while names like ResolverFn<T> or DataResolver do not exist at all, so mind the spelling. Attaching it takes a single property, built from five pieces: resolve: opens an object {, inside it a key you choose - samurai: - then the function samuraiResolver, then the closing }. Despite the name, a resolver settles no conflicts between routes, validates no URL parameters and performs no conditional redirects - redirectTo and guards do that. Its one job is to fetch data before activating a route, which is why you never see a profile flash empty. In the component you collect the finished data straight from the route:
export class SamuraiDetailComponent {
private route = inject(ActivatedRoute);
samurai = this.route.snapshot.data['samurai'] as Samurai;
}The key samurai in data is precisely the name you gave in resolve: { samurai: ... } - rename one and you must rename the other. Mind which bag you reach into as well: snapshot.data holds what the resolvers produced, while snapshot.params holds the raw values from the URL, so params['samurai'] would come back empty, and this.router.data or this.route.resolve are not real properties at all. Instead of calling a service and juggling a loading state inside the component, you just read a ready-made object. The trade-off is deliberate: the user waits a heartbeat longer for the transition but never stares at an empty screen - ideal for pages that mean nothing without their data.
Remember two things from this lesson: routes nest through children and render in the parent's router-outlet, like rooms inside the dojo; and a resolver fetches the data before you enter, so the component never starts out empty.
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. How do we define nested routes in Angular?
2. Where do child route components render?
These are 2 of 5 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Vertical ordering
Order the lines of a parent route definition with children (top to bottom, as in the editor):
- Code editor
Complete the configuration with nested routes for DashboardComponent.
- Click in order
Build the resolve property attaching a resolver under the 'samurai' key: