The Angular Router maps browser locations to component trees. Define route records, outlets, links, parameters, and programmatic navigation so URLs remain bookmarkable and direct refresh works after deployment.
Angular Router maps browser URLs to a tree of components. A navigation recognizes route segments, evaluates redirects and guards, resolves lazy dependencies and data, activates matched components, and updates browser history without a full document reload.
A route is configuration, not a menu item. RouterLink initiates navigation, RouterOutlet marks where an activated component renders, ActivatedRoute exposes state for the current route, and Router provides imperative navigation and events.
Define Routes in app.routes.ts and register them with provideRouter(routes) in ApplicationConfig. The document base href controls how relative application URLs resolve; most root-hosted applications use <base href="/">.
Put specific routes before a wildcard because Angular uses first-match wins. A redirect with an empty path normally needs pathMatch: "full" so it does not prefix-match every URL.
Child routes render in an outlet owned by their parent component. Lazy load a route with loadComponent or loadChildren when its code is not needed for the initial screen.
<base href="/">
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { DashboardComponent } from './dashboard/dashboard.component';
export const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'dashboard', component: DashboardComponent },
{ path: '**', redirectTo: 'home' }
];
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withComponentInputBinding())
]
};
<!-- Routed components will be display here -->
<router-outlet></router-outlet>
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterLink, RouterLinkActive, RouterOutlet],
template: `
<h1>Angular Router</h1>
<nav>
<a routerLink="/home" routerLinkActive="active">Home</a>
<a routerLink="/about" routerLinkActive="active">About</a>
<a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
</nav>
<router-outlet />
`
})
export class AppComponent { }
Use RouterLink in templates because it preserves normal link semantics and accessibility. Use Router.navigate for navigation triggered after TypeScript work such as a successful save, and Router.navigateByUrl when you already have a complete URL string.
Commands passed to navigate can be absolute or relative to an ActivatedRoute. NavigationExtras controls query parameters, fragments, history replacement, and browser state.
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-home',
standalone: true,
template: `
<h2>Home</h2>
<button (click)="goToDashboard()">Go to Dashboard</button>
`
})
export class HomeComponent {
private router = inject(Router);
goToDashboard() {
this.router.navigate(['/dashboard']);
}
}
ActivatedRoute exposes paramMap, queryParamMap, data, fragment, URL segments, parent, and children. Snapshot is suitable when the component cannot stay mounted while the value changes. Subscribe or convert the observable to a signal when the router may reuse the same component for a new parameter.
withComponentInputBinding can bind route parameters, query parameters, static data, and resolver data directly to matching component inputs. This keeps route-aware components easy to test because their data contract remains explicit.
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map } from 'rxjs';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-product-page',
template: `<p>Product ID: {{ productId() }}</p>`
})
export class ProductPageComponent {
private route = inject(ActivatedRoute);
productId = toSignal(
this.route.paramMap.pipe(map(params => params.get('id') ?? '')),
{ initialValue: '' }
);
}
The signal updates if navigation changes /products/1 to /products/2 while Angular reuses the same component instance.
Nested routes model a nested UI. The parent component stays active while a child component renders in its router-outlet. This is useful for settings, account, and admin shells with persistent navigation.
Route guards can improve navigation flow, but all authorization must be enforced by the backend. A user can modify client code and call an API directly.
export const routes: Routes = [
{
path: 'settings',
component: SettingsShellComponent,
children: [
{ path: '', redirectTo: 'profile', pathMatch: 'full' },
{ path: 'profile', component: ProfileSettingsComponent },
{ path: 'security', loadComponent: () =>
import('./security-settings.component')
.then(module => module.SecuritySettingsComponent) }
]
},
{ path: '**', component: NotFoundComponent }
];
SettingsShellComponent must import RouterOutlet. The security child is lazy while the parent shell and other child route stay available.
Router.events exposes the navigation lifecycle for diagnostics, analytics, and global progress. Filter for the event type required by the feature and bind imperative subscriptions with takeUntilDestroyed. Do not infer completion from a click because guards, redirects, resolvers, or lazy loading may still cancel or replace the navigation.
Set route title for document titles and extend TitleStrategy when the application needs a shared suffix or hierarchical rule. Configure scroll restoration and anchor scrolling through router features, then test browser back and forward behavior with long pages.
export const routes: Routes = [
{ path: '', title: 'Catalog', component: CatalogPage },
{ path: 'products/:id', title: 'Product', component: ProductPage }
];
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withInMemoryScrolling({
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled'
})
)
]
};
Test route recognition, redirects, bound inputs, guards, and rendered destinations through RouterTestingHarness with a focused route configuration. This proves the public navigation contract without starting a development server.
Use a browser or end-to-end test for real history traversal, scroll restoration, server fallback, base href, and direct URL refresh because those behaviors depend on the browser and deployment host.
TestBed.configureTestingModule({
providers: [
provideRouter([
{ path: 'products/:id', component: ProductPageComponent }
])
]
});
const harness = await RouterTestingHarness.create();
const page = await harness.navigateByUrl(
'/products/42',
ProductPageComponent
);
expect(page.productId()).toBe('42');
router.navigate() keeps the single-page application running, applies guards and resolvers, updates router state, and avoids downloading the app again. Assigning window.location asks the browser for a new document and restarts Angular.
The default matching strategy is prefix, and every URL begins with the empty string. An empty redirect can therefore match more routes than intended and repeatedly redirect navigation. pathMatch: 'full' tells Angular to apply the redirect only when the entire remaining URL is empty.
routerLinkActive uses prefix matching by default, so a link targeting / can appear active for every URL that begins at the root. Add routerLinkActiveOptions with exact: true to the home link when it should match only the exact home route.
Explore 500+ free tutorials across 20+ languages and frameworks.