Angular Routing
Understanding the core of Angular
Introduction
Section titled “Introduction”Angular Router, @angular/router, is the official library for managing navigation in Angular applications and a core part of the framework.
It is included by default in all projects created by Angular CLI.
When you navigate to a URL in your web browser, the browser normally makes a network request to a web server and displays the returned HTML page.
Within a single-page application (SPA), the browser only makes a request to a web server for the first page, the index.html.
After that, a client-side router takes over, controlling which content displays based on the URL.
When a user navigates to a different URL, the router updates the page’s content in place without triggering a full-page reload.
Routing in Angular is comprised of three primary parts:
- Routes define which component displays when a user visits a specific URL,
- Outlets are placeholders in your templates that dynamically load and render components based on the active route,
- Links provide a way for users to navigate between different routes in your application without triggering a full page reload.
Define routes
Section titled “Define routes”Routes serve as the fundamental building blocks for navigation within an Angular app.
In Angular, a route is an object that defines which component should render for a specific URL path or pattern, as well as additional configuration options about what happens when a user navigates to that URL.
Here is a basic example of a route:
import {AdminPage} from './app-admin';
const adminPage = { path: 'admin', component: AdminPage,};For this route, when a user visits the /admin path, the app will display AdminPage component.
Managing routes in your application: most projects define routes in a separate file that contains routes in the filename.
A collection of routes looks like this:
import {Routes} from '@angular/router';import {HomePage} from './home-page';import {AdminPage} from './about-page';
export const routes: Routes = [ { path: '', component: HomePage, }, { path: 'admin', component: AdminPage, },];If you generated a project with Angular CLI, your routes are defined in src/app/app.routes.ts.
When bootstrapping an Angular application without the Angular CLI, you can pass a configuration object that includes a providers array.
Inside of the providers array, you can add the Angular router to your application by adding a provideRouter function call with your routes.
import {ApplicationConfig} from '@angular/core';import {provideRouter} from '@angular/router';
import {routes} from './app.routes';
export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), // ... ],};Route URL Paths
Section titled “Route URL Paths”Static URL paths
Section titled “Static URL paths”Static URL paths refer to routes with predefined paths that don’t change based on dynamic parameters. These are routes that match a path string exactly and have a fixed outcome.
Examples of this include:
"/admin""/blog"
Define URL paths with Route Parameters
Section titled “Define URL paths with Route Parameters”Parameterized URLs allow you to define dynamic paths that allow multiple URLs to the same component while dynamically displaying data based on parameters in the URL.
You can define this type of pattern by adding parameters to your route’s path string and prefixing each parameter with the colon (:) character.
Please Note: Parameters are distinct from information in the URL’s query string.
The following example displays a user setting component based on the user id passed in through the URL.
import {Routes} from '@angular/router';import {UserSettings} from './user-settings/user-settings';
const routes: Routes = [{path: 'user/:id', component: UserSettings}];In this example, URLs such as /user/pit and /user/john render UserSettings component.
This component can then read the id parameter and use it to perform additional work, such as fetching data.
Valid route parameter names must start with a letter (a-z, A-Z) and can only contain:
- letters (
a-z,A-Z), - numbers (
0-9), - underscore (
_), - hyphen (
-).
You can also define paths with multiple parameters:
import {Routes} from '@angular/router';import {UserProfile} from './user-profile';import {SocialMediaFeed} from './social-media-feed';
const routes: Routes = [ {path: 'user/:id/:social-media', component: SocialMediaFeed}, {path: 'user/:id/', component: UserProfile},];With this new path, users can visit /user/pit/youtube and /user/pit/komoot and see respective social media feeds based on the parameter for the user pit.
See reading route state guide for details on reading route parameters.
Wildcards
Section titled “Wildcards”When you need to catch all routes for a specific path, the solution is a wildcard route which is defined with the double asterisk (**).
A common example is defining a PageNotFound component.
import {Home} from './home/home';import {UserProfile} from './user-profile';import {PageNotFound} from './page-not-found';
const routes: Routes = [ {path: 'home', component: Home}, {path: 'user/:id', component: UserProfile}, {path: '**', component: PageNotFound},];In this routes array, the app displays the PageNotFound component when the user visits any path outside of home and user/:id.
Wildcard routes are typically placed at the end of a routes array.
How Angular matches URLs
Section titled “How Angular matches URLs”When you define routes, the order is important because Angular uses a first-match wins strategy. This means that once Angular matches a URL with a route path, it stops checking any further routes. As a result, always put more specific routes before less specific routes.
The following example shows routes defined from most-specific to least specific:
const routes: Routes = [ {path: '', component: Home}, // Empty path {path: 'users/new', component: NewUser}, // Static, most specific {path: 'users/:id', component: UserDetail}, // Dynamic {path: 'users', component: Users}, // Static, less specific {path: '**', component: NotFound}, // Wildcard - always last];If a user visits /users/new, Angular router would go through the following steps:
- checks
''- doesn’t match - checks
users/new- matches, stops here - never reaches
users/:ideven though it could match - never reaches
users - never reaches
**
Redirects
Section titled “Redirects”You can define a route that redirects to another route instead of rendering a component:
import {Blog} from './home/blog';
const routes: Routes = [ { path: 'articles', redirectTo: '/blog', }, { path: 'blog', component: Blog, },];If you modify or remove a route, some users may still click on out-of-date links or bookmarks to that route. You can add a redirect to direct those users to an appropriate alternative route instead of a “not found” page.
Page titles
Section titled “Page titles”You can associate a title with each route. Angular automatically updates the page title when a route activates. Always define appropriate page titles for your application, as these titles are necessary to create an accessible experience.
import {Routes} from '@angular/router';import {Home} from './home';import {About} from './about';import {Places} from './places';
const routes: Routes = [ { path: '', component: Home, title: 'Home Page', }, { path: 'about', component: About, title: 'About Us', },];The page title property can be set dynamically to a resolver function using ResolveFn.
const titleResolver: ResolveFn<string> = (route) => route.queryParams['id'];const routes: Routes = [ ...{ path: 'places', component: Places, title: titleResolver, },];Route titles can also be set via a service extending the TitleStrategy abstract class.
By default, Angular uses the DefaultTitleStrategy.
Using TitleStrategy for page titles
Section titled “Using TitleStrategy for page titles”For advanced scenarios where you need centralized control over how the document title is composed, implement a TitleStrategy.
TitleStrategy is a token you can provide to override the default title strategy used by Angular.
You can supply a custom TitleStrategy to implement conventions such as adding an application suffix, formatting titles from breadcrumbs, or generating titles dynamically from route data.
import {inject, Injectable} from '@angular/core';import {Title} from '@angular/platform-browser';import {TitleStrategy, RouterStateSnapshot} from '@angular/router';
@Injectable()export class AppTitleStrategy extends TitleStrategy { private readonly title = inject(Title);
updateTitle(snapshot: RouterStateSnapshot): void { // PageTitle is equal to the "Title" of a route if it's set // If its not set it will use the "title" given in index.html const pageTitle = this.buildTitle(snapshot) || this.title.getTitle(); this.title.setTitle(`MyAwesomeApp - ${pageTitle}`); }}To use the custom strategy, provide it with the TitleStrategy token at the application level:
import {provideRouter, TitleStrategy} from '@angular/router';import {AppTitleStrategy} from './app-title.strategy';
export const appConfig = { providers: [provideRouter(routes), {provide: TitleStrategy, useClass: AppTitleStrategy}],};Route-level providers for dependency injection
Section titled “Route-level providers for dependency injection”Each route has a providers property that lets you provide dependencies to that route’s content via dependency injection.
Common scenarios where this can be helpful include applications that have different services based on whether the user is an admin or not.
export const ROUTES: Route[] = [ { path: 'admin', providers: [AdminService, {provide: ADMIN_API_KEY, useValue: '12345'}], children: [ {path: 'users', component: AdminUsers}, {path: 'teams', component: AdminTeams}, ], }, // other application routes that don't // have access to ADMIN_API_KEY or AdminService.];The admin path contains a protected data property of ADMIN_API_KEY that is only available to children within its section.
As a result, no other paths will be able to access the data provided via ADMIN_API_KEY.
Associating data with routes
Section titled “Associating data with routes”Route data enables you to attach additional information to routes. You are able to configure how components behave based on this data.
There are two ways to work with route data: static data that remains constant, and dynamic data that can change based on runtime conditions.
Static data
Section titled “Static data”You can associate arbitrary static data with a route via the data property in order to centralize things like route-specific metadata (e.g., analytics tracking, permissions, etc.):
import {Routes} from '@angular/router';import {Home} from './home';import {About} from './about';import {Places} from './places';
const routes: Routes = [ { path: 'about', component: About, data: {analyticsId: '456'}, }, { path: '', component: Home, data: {analyticsId: '123'}, },];In this code sample, the home and about page are configured with specific analyticsId which would then be used in their respective components for page tracking analytics.
You can read this static data by injecting the ActivatedRoute. See Reading route state for details.
Dynamic data with data resolvers
Section titled “Dynamic data with data resolvers”When you need to provide dynamic data to a route, check out the guide on route data resolvers.
Nested Routes
Section titled “Nested Routes”Nested routes, also known as child routes, are a common technique for managing more complex navigation routes where a component has a sub-view that changes based on the URL.
You can add child routes to any route definition with the children property:
const routes: Routes = [ { path: 'product/:id', component: Product, children: [ { path: 'info', component: ProductInfo, }, { path: 'reviews', component: ProductReviews, }, ], },];The above example defines a route for a product page that allows a user to change whether the product info or reviews are displayed based on the url.
The children property accepts an array of Route objects.
To display child routes, the parent component (Product in the example above) includes its own <router-outlet>.
<!-- Product --><article> <h1>Product {{ id }}</h1> <router-outlet /></article>Navigation between URLs that match the child routes updates only the nested outlet.
Read route state
Section titled “Read route state”Get information about the current route
Section titled “Get information about the current route”ActivatedRoute is a service from @angular/router that provides all the information associated with the current route.
import {Component} from '@angular/core';import {ActivatedRoute} from '@angular/router';
@Component({ selector: 'app-product',})export class Product { private activatedRoute = inject(ActivatedRoute);
constructor() { console.log(this.activatedRoute); }}ActivatedRoute can provide different information about the route.
Some common properties include:
| Property | Details |
|---|---|
url | Observable of the route paths, represented as an array of strings for each part of the route path. |
data | Observable that contains the data object provided for the route. Also contains any resolved values from the resolve guard. |
params | Observable that contains the required and optional parameters specific to the route. |
queryParams | Observable that contains the query parameters available to all routes. |
Route snapshots
Section titled “Route snapshots”Page navigations are events over time: you can access the router state at a given time by retrieving a route snapshot.
Route snapshots contain essential information about the route, including its parameters, data, and child routes. In addition, snapshots are static and will not reflect future changes.
Here’s an example of how you would access a route snapshot:
import {ActivatedRoute, ActivatedRouteSnapshot} from '@angular/router';
@Component({/*...*/})export class UserProfile { readonly userId: string; private route = inject(ActivatedRoute);
constructor() { // Example URL: https://www.angular.dev/users/123?role=admin&status=active#contact
// Access route parameters from snapshot this.userId = this.route.snapshot.paramMap.get('id');
// Access multiple route elements const snapshot = this.route.snapshot; console.log({ url: snapshot.url, // https://www.angular.dev // Route parameters object: {id: '123'} params: snapshot.params, // Query parameters object: {role: 'admin', status: 'active'} queryParams: snapshot.queryParams, // Query parameters }); }}Reading parameters on a route
Section titled “Reading parameters on a route”There are two types of parameters that developers can utilize from a route: route and query parameters.
Route Parameters
Section titled “Route Parameters”Route parameters allow you to pass data to a component through the URL. This is useful when you want to display specific content based on an identifier in the URL, like a user ID or a product ID.
You can define route parameters by prefixing the parameter name with a colon (:).
import {Routes} from '@angular/router';import {Product} from './product';
const routes: Routes = [{path: 'product/:id', component: Product}];You can access parameters by subscribing to route.params.
import {Component, inject, signal} from '@angular/core';import {ActivatedRoute} from '@angular/router';
@Component({ selector: 'app-product-detail', template: `<h1>Product Details: {{ productId() }}</h1>`,})export class ProductDetail { productId = signal(''); private activatedRoute = inject(ActivatedRoute);
constructor() { // access route parameters this.activatedRoute.params.subscribe((params) => { this.productId.set(params['id']); }); }}Query Parameters
Section titled “Query Parameters”Query parameters provide a flexible way to pass optional data through URLs without affecting the route structure. Query parameters can persist across navigation events and are perfect for handling filtering, sorting, pagination, and other stateful UI elements.
// single parameter structure// /places?category=seasiderouter.navigate(['/places'], { queryParams: {category: 'seaside'},});
// Multiple parameters// /places?category=seaside&sort=price&page=1router.navigate(['/places'], { queryParams: { category: 'seaside', sort: 'price', page: 1, },});You can access query parameters with route.queryParams.
Here is an example of a ProductList that updates the query parameters that affect how it displays a list of places:
import {ActivatedRoute, Router} from '@angular/router';
@Component({ selector: 'app-product-list', template: ` <div> <select (change)="updateSort($event)"> <option value="price">Price</option> <option value="name">Name</option> </select> <!-- Places list --> </div> `,})export class ProductList { private route = inject(ActivatedRoute); private router = inject(Router);
constructor() { // Access query parameters reactively this.route.queryParams.subscribe((params) => { const sort = params['sort'] || 'price'; const page = Number(params['page']) || 1; this.loadPlaces(sort, page); }); }
updateSort(event: Event) { const sort = (event.target as HTMLSelectElement).value; // Update URL with new query parameter this.router.navigate([], { queryParams: {sort}, queryParamsHandling: 'merge', // Preserve other query parameters }); }}In this example, users can use a select element to sort the product list by name or price. The associated change handler updates the URL’s query parameters, which in turn triggers a change event that can read the updated query parameters and update the product list.
Matrix Parameters
Section titled “Matrix Parameters”Matrix parameters:
- are optional parameters that belong to a specific URL segment,
- use semicolons (
;) and are scoped to individual path segments, - are useful when you need to pass auxiliary data to a specific route segment without affecting the route definition or matching behavior.
Like query parameters, they don’t need to be defined in your route configuration.
// URL format: /path;key=value// Multiple parameters: /path;key1=value1;key2=value2// Navigate with matrix parametersthis.router.navigate(['/awesome-places', {view: 'grid', filter: 'new'}]);// Results in URL: /awesome-places;view=grid;filter=newUsing ActivatedRoute
import {Component, inject} from '@angular/core';import {ActivatedRoute} from '@angular/router';@Component(/* ... */)export class AwesomePlaces { private route = inject(ActivatedRoute); constructor() { // Access matrix parameters via params this.route.params.subscribe((params) => { const view = params['view']; // e.g., 'grid' const filter = params['filter']; // e.g., 'new' }); }}Detect active current route
Section titled “Detect active current route”You can use RouterLinkActive directive to dynamically style navigation elements based on the current active route.
This is common in navigation elements to inform users what the active route is.
<nav> <a class="button" routerLink="/about" routerLinkActive="active-button" ariaCurrentWhenActive="page" > About </a> - <a class="button" routerLink="/settings" routerLinkActive="active-button" ariaCurrentWhenActive="page" > Settings </a></nav>Angular Router will apply the active-button class to the correct anchor link and ariaCurrentWhenActive to page when the URL matches the corresponding routerLink.
If you need to add multiple classes onto the element, you can use either a space-separated string or an array:
<!-- Space-separated string syntax --><a routerLink="/user/bob" routerLinkActive="class1 class2">Bob</a><!-- Array syntax --><a routerLink="/user/bob" [routerLinkActive]="['class1', 'class2']">Bob</a>When you specify a value for routerLinkActive, you are also defining the same value for ariaCurrentWhenActive.
This makes sure that visually impaired users (which may not perceive the different styling being applied) can also identify the active button.
If you want to define a different value for aria, you’ll need to explicitly set the value using the ariaCurrentWhenActive directive.
Route matching strategy
Section titled “Route matching strategy”By default, RouterLinkActive considers any ancestors in the route a match.
<a [routerLink]="['/user/pit']" routerLinkActive="active-link"> User </a><a [routerLink]="['/user/pit/role/admin']" routerLinkActive="active-link"> Role </a>When the user visits /user/pit/role/admin, both links would have the active-link class.
Only apply RouterLinkActive on exact route matches
Section titled “Only apply RouterLinkActive on exact route matches”If you only want to apply the class on an exact match, you need to provide the routerLinkActiveOptions directive with a configuration object that contains the value exact: true.
<a [routerLink]="['/user/pit']" routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: true}"> User</a><a [routerLink]="['/user/pit/role/admin']" routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: true}"> Role</a>If you want to be more precise in how a route is matched, it’s worth noting that exact: true is actually syntactic sugar for the full set of matching options:
// `exact: true` is equivalent to{ paths: 'exact', fragment: 'ignored', matrixParams: 'ignored', queryParams: 'exact',}// `exact: false` is equivalent{ paths: 'subset', fragment: 'ignored', matrixParams: 'ignored', queryParams: 'subset',}Apply RouterLinkActive to an ancestor
Section titled “Apply RouterLinkActive to an ancestor”RouterLinkActive directive can also be applied to an ancestor element in order to allow developers to style the elements as desired.
<div routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: true}"> <a routerLink="/user/jim">Jim</a> <a routerLink="/user/bob">Bob</a></div>Check if a URL is active
Section titled “Check if a URL is active”isActive function returns a computed signal that tracks whether a given URL is currently active in the router.
The signal automatically updates as the router state changes.
import {Component, inject} from '@angular/core';import {isActive, Router} from '@angular/router';@Component({ template: ` <div [class.active]="isSettingsActive()"> <h2>Settings</h2> </div> `,})export class Panel { private router = inject(Router); isSettingsActive = isActive('/settings', this.router, { paths: 'subset', queryParams: 'ignored', fragment: 'ignored', matrixParams: 'ignored', });}