Angular Complete Route Configuration: Every Route Property Explained [2026]
The Angular Route object has about twenty properties, but most tutorials only show the three you need for hello-world (path, component, redirectTo). The other seventeen are where real applications live — nested routes, named outlets, route-level providers, runtime data, and the matcher options that decide which route wins. This lesson is the reference page you come back to.
This is lesson 5.2, following lesson 5.1's router essentials. Treat this lesson as a lookup. You won't memorize every property; you'll grep this page when a Route doesn't behave the way you expect.
The full table #
Every property on the Route type, with what it does:
| Property | Type | Purpose |
|---|---|---|
path |
string |
URL segment to match |
component |
Type<Component> |
Component to render |
loadComponent |
() => Promise<...> |
Lazy-loaded component (lesson 5.6) |
loadChildren |
() => Promise<Routes> |
Lazy-loaded sub-routes |
children |
Routes |
Nested routes; render inside this route's component |
redirectTo |
string | (...) => UrlTree |
Redirect to another URL or function-computed UrlTree |
pathMatch |
'prefix' | 'full' |
How path should match (default prefix) |
outlet |
string |
Named outlet (default unnamed/primary) |
matcher |
UrlMatcher |
Fully-custom URL matching function |
data |
any |
Static data attached to the route, available as input or via ActivatedRoute |
resolve |
Record<string, ResolveFn> |
Async data resolvers — runs before route activates (lesson 5.5) |
canActivate |
CanActivateFn[] |
Functional guards — must return true for route to activate (lesson 5.3) |
canActivateChild |
CanActivateChildFn[] |
Like canActivate but for any descendant route |
canDeactivate |
CanDeactivateFn<T>[] |
Run when leaving the route — block navigation if returns false |
canMatch |
CanMatchFn[] |
Run BEFORE matching — useful for feature flags |
providers |
Provider[] |
DI providers scoped to this route + its children |
title |
string | ResolveFn<string> |
Document title for this route |
runGuardsAndResolvers |
enum | When to re-run guards/resolvers on same-URL navigation |
Twenty properties. Most routes use 2-4 of them. The rare ones earn their keep when you need them.
pathMatch: 'prefix' | 'full' #
The matcher defaults to prefix — a route matches if its path is a PREFIX of the URL. With full, the path must match the ENTIRE remaining URL.
The distinction matters most for empty-path redirects:
// ❌ Wrong — matches EVERYTHING (every URL starts with '')
{ path: '', redirectTo: '/home', pathMatch: 'prefix' }
// ✅ Right — matches only the empty path
{ path: '', redirectTo: '/home', pathMatch: 'full' }
When redirectTo is set, the Angular compiler will error if pathMatch: 'full' is missing on an empty-path route. For non-empty paths the prefix default is almost always what you want.
children — nested routes #
A route can have child routes that render inside ITS <router-outlet />:
export const routes: Routes = [
{
path: 'settings',
component: SettingsShellPage,
children: [
{ path: 'profile', component: ProfilePage },
{ path: 'security', component: SecurityPage },
{ path: '', redirectTo: 'profile', pathMatch: 'full' },
],
},
];
In SettingsShellPage's template:
<nav>
<a routerLink="profile">Profile</a> <!-- relative — resolves to /settings/profile -->
<a routerLink="security">Security</a>
</nav>
<router-outlet />
The shell renders consistently; the outlet swaps the child. The empty-path redirect ensures /settings auto-redirects to /settings/profile.
Nested routes scale to any depth — useful for admin areas, settings pages, multi-step wizards.
outlet — named (secondary) outlets #
A route can render into a NAMED outlet instead of the primary one:
<router-outlet /> <!-- primary -->
<router-outlet name="sidebar" /> <!-- named -->
export const routes: Routes = [
{ path: '', component: HomePage },
{ path: 'help', component: HelpPanel, outlet: 'sidebar' },
];
Navigate the named outlet via URL syntax:
<a [routerLink]="[{ outlets: { sidebar: ['help'] } }]">Show help</a>
The URL becomes /(sidebar:help). Use for persistent panels — sidebars, modal slots, chat windows — that change independently of the main content.
Rare in everyday apps; useful for app shells with multiple independently-routed regions.
data — static route metadata #
Attach arbitrary data to a route:
{
path: 'admin',
component: AdminDashboard,
data: { requiresRole: 'admin', showSidebar: false },
}
With withComponentInputBinding() (lesson 5.1), the component receives data as inputs:
export class AdminDashboard {
requiresRole = input<string>();
showSidebar = input<boolean>();
}
Used for breadcrumbs, role requirements consumed by guards, feature flags, page titles, anything route-tied that the component or a guard needs at runtime.
providers — route-scoped DI #
A route can register providers visible only to itself and its children:
{
path: 'shop',
component: ShopPage,
providers: [
CartService, // new instance per shop route
{ provide: API_BASE, useValue: '/api/shop' },
],
children: [...],
}
The CartService is a fresh instance when the user enters /shop; it dies when they leave. Useful for feature-scoped state (a shopping cart that resets when the user navigates away) and for environment-token overrides per area of the app.
The alternative — providing at the root and managing lifecycle yourself — works but couples the service's lifetime to the app's. Route-scoped providers tie the lifetime to the user's presence on the route.
redirectTo with a function #
Classic string-form redirect:
{ path: 'old-path', redirectTo: '/new-path', pathMatch: 'full' }
v16+ adds function-form redirect for dynamic logic:
{
path: 'old/:id',
redirectTo: ({ params }) => `/new/${params['id']}`,
pathMatch: 'full',
}
Return a string or a UrlTree. Useful for renaming routes while preserving the dynamic segments.
runGuardsAndResolvers — when to re-run #
Default: when the user navigates to the SAME URL, guards and resolvers don't re-run (Angular assumes nothing changed). Override:
{
path: 'dashboard',
component: DashboardPage,
resolve: { data: dashboardResolver },
runGuardsAndResolvers: 'always',
}
Options:
'paramsChange'(default) — re-run only if path params change'paramsOrQueryParamsChange'— also if query params change'always'— every navigation, even same-URL'pathParamsChange'/'pathParamsOrQueryParamsChange'— refined variants
Use 'always' for routes whose data is time-sensitive (a stale dashboard the user wants to refresh) and you want clicking the same nav link to re-fetch.
canMatch vs canActivate — when each runs #
Both gate route activation but at different times:
| Guard | When it runs | Failure result |
|---|---|---|
canMatch |
BEFORE Angular even considers this route — during URL matching | Route is skipped; the matcher tries the next one |
canActivate |
AFTER the route is matched, BEFORE the component renders | Navigation is cancelled |
Use canMatch for feature flags ("if the beta feature is off, this route doesn't exist") and for routes that should fall through to a wildcard. Use canActivate for auth ("this route exists, but you can't access it without being logged in — redirect to login").
Lesson 5.3 covers the full guard API.
A realistic route config #
export const routes: Routes = [
// Public
{ path: '', component: HomePage, title: 'Welcome' },
{ path: 'login', component: LoginPage, title: 'Sign in', canActivate: [redirectIfLoggedIn] },
// Authenticated area with nested admin
{
path: 'app',
component: AppShell,
canActivate: [authGuard],
children: [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: Dashboard, title: 'Dashboard', resolve: { data: dashboardResolver } },
{ path: 'users', component: UserListPage, title: 'Users' },
{ path: 'users/:id', component: UserDetailPage },
{
path: 'admin',
component: AdminShell,
canActivate: [adminGuard],
providers: [AdminApiClient],
children: [
{ path: 'roles', component: RolesPage },
{ path: 'settings', component: AdminSettingsPage },
],
},
],
},
// Beta feature — only matches if flag is on
{ path: 'beta-feature', component: BetaPage, canMatch: [betaFlagMatcher] },
// 404
{ path: '**', component: NotFoundPage, title: 'Not found' },
];
Real patterns at work:
pathMatch: 'full'on the empty-path redirect inside/appchildrenfor nested routes —/app/dashboard,/app/users, etc.canActivate: [authGuard]on the parent — auth required for the whole/appsubtree- Stacked guards (
canActivate: [authGuard]on/app,canActivate: [adminGuard]on/app/admin) - Route-scoped
providersfor admin-only services canMatchfor beta features that disappear from routing entirely when off- Wildcard
**for 404
Most real-world apps look like this — a few public routes, an authenticated shell with nested children, a 404 catch-all.
Common gotchas #
| Symptom | Cause | Fix |
|---|---|---|
| Empty-path route doesn't redirect | Missing pathMatch: 'full' |
Required for redirectTo on '' |
| Wildcard never matches | ** is not LAST in the routes array |
Reorder — ** always last |
| Nested route 404s | Missing <router-outlet /> in the parent component's template |
Add it |
| Same-URL navigation does nothing | Default runGuardsAndResolvers: 'paramsChange' |
Set 'always' for time-sensitive routes |
| Route-scoped providers don't see updates | Each navigation reuses the same instance unless you exit/re-enter the route | Provider lifetime = route activation lifetime; refresh by exit/re-enter |
| Named outlet doesn't render | Outlet missing name="...", or routerLink syntax wrong |
Both need to use the same outlet name |
What's next #
Lesson 5.3 covers functional route guards in depth — CanActivateFn, CanDeactivateFn, CanMatchFn, ResolveFn. Lesson 5.4 catalogs router events (the 11 events fired during navigation). Lesson 5.5 covers data resolvers and prefetching. Lesson 5.6 closes Module 5 with lazy loading.
Try it yourself #
A mini app with nested routes, route data, and a route-scoped provider:
import { Component, inject, input } from '@angular/core';
import { provideRouter, withComponentInputBinding, RouterOutlet, RouterLink } from '@angular/router';
import { bootstrapApplication } from '@angular/platform-browser';
import { Injectable } from '@angular/core';
@Injectable() class FeatureService { name = 'I am route-scoped'; }
@Component({
imports: [RouterOutlet, RouterLink],
template: `
<h2>Settings</h2>
<nav><a routerLink="profile">Profile</a> | <a routerLink="security">Security</a></nav>
<router-outlet />
<p><small>Service says: {{ svc.name }}</small></p>
`,
})
class SettingsShell { svc = inject(FeatureService); }
@Component({ template: `<p>Profile page (section: {{ section() }})</p>` })
class ProfilePage { section = input.required<string>(); }
@Component({ template: `<p>Security page</p>` })
class SecurityPage {}
@Component({
selector: 'app-root',
imports: [RouterOutlet, RouterLink],
template: `<a routerLink="/settings">Settings</a> | <a routerLink="/">Home</a><router-outlet />`,
})
class App {}
bootstrapApplication(App, {
providers: [provideRouter([
{ path: '', component: SettingsShell, redirectTo: 'settings', pathMatch: 'full' },
{
path: 'settings',
component: SettingsShell,
providers: [FeatureService],
data: { section: 'settings' },
children: [
{ path: '', redirectTo: 'profile', pathMatch: 'full' },
{ path: 'profile', component: ProfilePage },
{ path: 'security', component: SecurityPage },
],
},
], withComponentInputBinding())],
});
Every property in this lesson — nested routes, empty-path redirect, route data, route-scoped providers — in one runnable example.
providedIn: 'root' when the service holds app-wide state (auth, theme, user prefs). The rule: if you’d want the state to reset on next navigation, scope it. If it should persist across routes, root it.Lesson 5.3 picks up functional route guards in depth.
Up next in Angular
More from this topic
Enjoyed this article?
Get new Angular tutorials delivered. No spam — just code-first articles when they ship.


