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 | RedirectFunction |
Redirect to another URL, or a function returning a string/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 on an empty-path route without pathMatch, the router throws a configuration error in dev mode as soon as the routes are registered. 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 instance is created the first time the user enters /shop and is shared by /shop and its children. By default the route's injector is NOT destroyed when the user leaves — re-entering /shop gets the same instance. If you want the state to reset on exit, opt in with provideRouter(routes, withAutoCleanupInjectors()) (v22.2; earlier v22 releases call it withExperimentalAutoCleanupInjectors()), which destroys injectors of inactive routes.
Route-scoped providers are great for feature isolation (the service is invisible outside /shop) and for environment-token overrides per area of the app — and, with auto-cleanup enabled, for state tied to the user's presence on the route.
redirectTo with a function #
Classic string-form redirect:
{ path: 'old-path', redirectTo: '/new-path', pathMatch: 'full' }
v18+ 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: guards and resolvers re-run only when path or matrix params change. 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). Note that navigating to the exact same URL is ignored by default — for clicking the same nav link to re-fetch, also set withRouterConfig({ onSameUrlNavigation: 'reload' }).
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 onSameUrlNavigation: 'ignore' plus runGuardsAndResolvers: 'paramsChange' |
withRouterConfig({ onSameUrlNavigation: 'reload' }) + runGuardsAndResolvers: 'always' |
| Route-scoped service keeps stale state after leaving | Route injectors are not destroyed by default | Add withAutoCleanupInjectors(), or reset the state explicitly |
| 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 (every event 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: '', redirectTo: 'settings', pathMatch: 'full' }, // redirectTo and component can't be combined
{
path: 'settings',
component: SettingsShell,
providers: [FeatureService],
children: [
{ path: '', redirectTo: 'profile', pathMatch: 'full' },
{ path: 'profile', component: ProfilePage, data: { section: 'settings' } }, // child routes don't inherit parent data by default
{ path: 'security', component: SecurityPage },
],
},
], withComponentInputBinding())],
});
Every property in this lesson — nested routes, empty-path redirect, route data, route-scoped providers — in one runnable example.
search_documentationRoute-scoped when the service belongs to one area of the app: a wizard’s draft data, a feature flag override, a per-feature API client with its own base URL. Note that route injectors survive navigation by default — add withAutoCleanupInjectors() if the state should die when the user leaves. providedIn: 'root' when the service holds app-wide state (auth, theme, user prefs). The rule: if only one area should see it, scope it (plus auto-cleanup if it must reset). If it should persist across the whole app, 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.


