Routing in Angular
By Syncfusion®, Inc. · Last updated
Mapping URLs to components with the Angular Router: route configuration, router-outlet, routerLink, programmatic navigation, redirects, route and query parameters, child, wildcard and lazy-loaded routes.
What Is Routing?
Routing connects each URL to one screen. For example, /products opens the product list and /cart opens the cart.
An Angular app is a single-page application: the browser downloads one page, and Angular changes the content inside it. Routing gives each screen its own URL without loading a new page.
Real-World Example
In a project management app, /projects/42/tasks opens the task board of project 42. A colleague who opens this link sees the same board.
The Back button returns to the project list, and the header and menu never reload.
/products/7URLRouterreads the URLproducts/:idmatching routeProductDetailshown in the outletOnly the outlet changes. The header and menu stay on the screen.
From URL to component.
Why Routing?
Without routing, the URL never changes. So the user cannot save or share a screen, and the browser's Back button closes the app.
Benefits of routing:
- Shareable URLs: each screen has its own URL. Users can save it as a bookmark or send it to another person.
- Back and Forward buttons work the same way as on other websites.
- Fast screen changes: Angular replaces only the part of the page that changes. The browser does not download a new page.
- Clear structure: each screen is one component, and one list connects each URL to its component.
- Faster start: the browser can download a screen only when the user opens it. This is called lazy loading, and you learn it at the end of this lesson.
| Point | Without routing | With routing |
|---|---|---|
| How the screen changes | @if blocks and a variable | The URL chooses the screen |
| User refreshes the page | The app opens the first screen again | The same screen opens again |
| Save or share a screen | Not possible | Copy the URL |
| Browser's Back button | Leaves the app | Opens the previous screen |
Angular Router
The Angular Router is the part of Angular that does routing. It is in the package @angular/router.
The router does three things:
- It reads the URL in the browser.
- It finds the route that matches this URL.
- It displays the component of that route.
You use these parts of the router in this lesson:
Routes: the list of routes. Each route connects a URL path to a component.provideRouter(routes): turns on the router and gives it your list of routes.<router-outlet />: the place in the template where the router displays the component.routerLink: makes a link open a route without reloading the page.Router: a service that opens a route from your TypeScript code.withComponentInputBinding(): gives the values from the URL to the component's@Input()properties.
Setting Up Routing
You do not need to install anything. When you create a project with ng new, Angular prepares routing for you. To use it, you work with three things.
Step 1: Write Your Routes in app.routes.ts
app.routes.ts holds the list of your routes. In a new project, the list is empty ([]):
// app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [];
You add one object to this list for each screen of your app. The next section shows how.
Step 2: The Router Gets Your List in app.config.ts
app.config.ts contains the settings that Angular reads when the app starts. Here, provideRouter(routes) turns on the router and gives it your list of routes:
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)],
};
The CLI already wrote this file. You do not need to change it.
Step 3: Add <router-outlet /> to the App Template
The router needs a place on the screen where it can display the components. You mark this place with <router-outlet /> in the template of the App component. You learn it in the section "Router Outlet" below.
In the practice blocks below, the list of routes is written in app.ts, not in app.routes.ts. This way you can read all the code in one file. The router still gets the list in the same way.
Route Configuration
Each route is an object. Most routes have two properties: path and component.
export const routes: Routes = [
{ path: '', component: Home }, // localhost:4200/
{ path: 'products', component: Products }, // localhost:4200/products
{ path: 'cart', component: Cart, title: 'Your cart' },
];
What each property does:
pathis the part of the URL after the domain. Write it without/at the start. The empty path''is the home page.componentis the component that the router displays when the URL matches the path.titleis the text in the browser tab for this page.
The Order of Routes
The router checks the routes from top to bottom and uses the first route that matches. If two routes match the same URL, the lower route is never used.
Router Outlet (router-outlet)
<router-outlet /> marks the place in the template where the router displays the component of the current route.
The content outside the outlet, such as a header or a footer, stays on the screen. Only the content in the outlet changes when the URL changes.
Routes and an outlet
import { Component } from '@angular/core';
import { Routes, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-home',
template: `<p>Welcome to Gadget Hub. Browse our latest products.</p>`,
})
export class Home {}
export const routes: Routes = [
{ path: '', component: Home },
];
@Component({
selector: 'app-root',
imports: [RouterOutlet],
template: `
<header><b>Gadget Hub</b></header>
<router-outlet />
<footer>© Gadget Hub</footer>
`,
})
export class App {}
The URL is empty, so the router uses the route with path: ''. It displays the Home component between the header and the footer.
RouterLink
A normal link with href makes the browser download the whole app again. A link with routerLink tells the router to change the URL and change the component in the outlet. The page does not reload.
How to write it:
- For a fixed path, write
routerLink="/products". - For a path with a value, use an array:
[routerLink]="['/products', product.id]". This creates the path/products/7whenproduct.idis 7. - Add
routerLinkActive="active"to add the CSS classactiveto the link while its route is open. Use it to highlight the current menu item.
Navigating with routerLink
import { Component } from '@angular/core';
import { Routes, RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
@Component({ selector: 'app-home', template: `<p>Home: today's deals.</p>` })
export class Home {}
@Component({ selector: 'app-products', template: `<p>Products: laptops, phones and tablets.</p>` })
export class Products {}
@Component({ selector: 'app-cart', template: `<p>Cart: 2 items.</p>` })
export class Cart {}
export const routes: Routes = [
{ path: '', component: Home },
{ path: 'products', component: Products },
{ path: 'cart', component: Cart },
];
@Component({
selector: 'app-root',
imports: [RouterOutlet, RouterLink, RouterLinkActive],
template: `
<nav>
<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Home</a> |
<a routerLink="/products" routerLinkActive="active">Products</a> |
<a routerLink="/cart" routerLinkActive="active">Cart</a>
</nav>
<router-outlet />
`,
styles: `.active { font-weight: bold; color: #c3002f; }`,
})
export class App {}
Click the links. The text below the menu changes to the selected page, and the link of the open page becomes bold and red.
For the home link, add [routerLinkActiveOptions]="{ exact: true }". Without it, the home link stays highlighted on every page, because every URL starts with /.
Programmatic Navigation
Programmatic navigation means opening a route from TypeScript code, not from a link. For example, after the user places an order, the code opens the "Order confirmed" page.
To do this, inject the Router service and call one of these methods:
| Method | Example | When to use it |
|---|---|---|
navigate() | router.navigate(['/orders', id]) | You build the path from separate parts |
navigateByUrl() | router.navigateByUrl('/orders/7') | You already have the full path as text |
Navigating after an action
import { Component, inject } from '@angular/core';
import { Router, Routes, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-checkout',
template: `
<p>Checkout: 2 items, total $55</p>
<button (click)="placeOrder()">Place order</button>
`,
})
export class Checkout {
private router = inject(Router);
placeOrder() {
const orderId = 1042; // in a real app, returned by the server
this.router.navigate(['/order-confirmed', orderId]);
}
}
@Component({ selector: 'app-confirmed', template: `<p>Thank you! Your order is confirmed.</p>` })
export class Confirmed {}
export const routes: Routes = [
{ path: '', component: Checkout },
{ path: 'order-confirmed/:id', component: Confirmed },
];
@Component({
selector: 'app-root',
imports: [RouterOutlet],
template: `<router-outlet />`,
})
export class App {}
Click Place order. The placeOrder() method calls navigate(), and the router displays the confirmation page.
Redirect Routes
A redirect route sends the user from one URL to another URL. It is often used to open a start page when the URL is empty.
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{ path: 'home', redirectTo: 'dashboard' },
redirectTois the path to open instead.pathMatch: 'full'means: redirect only when the whole URL is empty. Without it, the empty path matches the start of every URL, and the redirect runs on every page.- Redirects also help when you rename a page. Users with an old bookmark still reach the new page.
Route Parameters
A route parameter is a part of the path that can change. You write it with a colon (:) before its name, for example products/:id.
The URLs /products/7 and /products/12 both match products/:id. For the first URL, id is '7'. For the second URL, id is '12'. One route and one component can display any product.
Read the Parameter with @Input()
You know @Input() from the lesson Component Communication. A parent component uses it to give a value to a child component.
The router can give values in the same way. It puts the value of a route parameter into an @Input() property that has the same name. For the route products/:id, write this in the component:
@Input() id = '';
To turn on this feature, add withComponentInputBinding() to provideRouter() in app.config.ts:
providers: [provideRouter(routes, withComponentInputBinding())],
The practice blocks already have this setting.
When the Parameter Changes
The user can go from /products/7 to /products/12. Both URLs use the same route, so Angular keeps the same component on the screen. The router only changes the value of id from '7' to '12', and the template displays the new product.
Reading a route parameter
import { Component, Input } from '@angular/core';
import { Routes, RouterOutlet, RouterLink } from '@angular/router';
const PRODUCTS: Record<string, string> = { '7': 'Wireless Mouse', '12': 'Mechanical Keyboard' };
@Component({
selector: 'app-product-detail',
template: `<p>Product #{{ id }}: {{ name() }}</p>`,
})
export class ProductDetail {
@Input() id = '';
name() {
return PRODUCTS[this.id] ?? 'Not found';
}
}
export const routes: Routes = [
{ path: '', redirectTo: 'products/7', pathMatch: 'full' },
{ path: 'products/:id', component: ProductDetail },
];
@Component({
selector: 'app-root',
imports: [RouterOutlet, RouterLink],
template: `
<a routerLink="/products/7">Mouse</a> | <a routerLink="/products/12">Keyboard</a>
<router-outlet />
`,
})
export class App {}
The empty URL redirects to products/7. Click Keyboard. The component stays on the screen and displays product 12.
Query Parameters
Query parameters are extra values at the end of the URL, after a ? sign. For example: /search?q=laptop&page=2. Each value has a name (q, page) and a value (laptop, 2).
Use query parameters for filters, sorting and page numbers. The screen stays the same, but it shows different data.
| Point | Route parameter | Query parameter |
|---|---|---|
| Example URL | /products/7 | /products?sort=price |
| Written in the route's path | Yes: products/:id | No |
| Required | Yes. Without it, the route does not match | No |
| Common use | Which item to display | Filter, sort order, page number |
In a link, add query parameters with [queryParams]. In the component, read them with an @Input() property that has the same name, the same as route parameters.
A query parameter can be missing from the URL. Then the property is undefined, so write a ? after its name: @Input() category?: string;.
Filtering with query parameters
import { Component, Input } from '@angular/core';
import { Routes, RouterOutlet, RouterLink } from '@angular/router';
const ALL = [
{ name: 'Laptop', category: 'computers' },
{ name: 'Monitor', category: 'computers' },
{ name: 'Headphones', category: 'audio' },
];
@Component({
selector: 'app-products',
template: `
<p>Category: {{ category ?? 'all' }}</p>
@for (p of list(); track p.name) {
<p>• {{ p.name }}</p>
}
`,
})
export class Products {
@Input() category?: string;
list() {
return this.category ? ALL.filter(p => p.category === this.category) : ALL;
}
}
export const routes: Routes = [{ path: '', component: Products }];
@Component({
selector: 'app-root',
imports: [RouterOutlet, RouterLink],
template: `
<a routerLink="/">All</a> |
<a routerLink="/" [queryParams]="{ category: 'computers' }">Computers</a> |
<a routerLink="/" [queryParams]="{ category: 'audio' }">Audio</a>
<router-outlet />
`,
})
export class App {}
Click Computers or Audio. The URL gets ?category=..., and the list shows only the products of that category.
From TypeScript code, pass query parameters as an option: router.navigate(['/products'], { queryParams: { category: 'audio' } }).
Child Routes
Child routes are routes inside another route. They display a component inside another component.
The parent component has its own <router-outlet />. The routes in its children list are displayed in this outlet. A settings page with tabs is a common example: the tabs stay on the screen, and the content below them changes.
App: header + <router-outlet>Settings (path: settings): tabs + <router-outlet>Profilesettings/profileSecuritysettings/securityA child route renders inside its parent's outlet, so the parent's tabs stay visible.
Child routes render inside the parent component.
A settings page with child routes
import { Component } from '@angular/core';
import { Routes, RouterOutlet, RouterLink } from '@angular/router';
@Component({ selector: 'app-profile', template: `<p>Profile: name, email and photo.</p>` })
export class Profile {}
@Component({ selector: 'app-security', template: `<p>Security: password and two-factor login.</p>` })
export class Security {}
@Component({
selector: 'app-settings',
imports: [RouterOutlet, RouterLink],
template: `
<h4>Settings</h4>
<a routerLink="profile">Profile</a> | <a routerLink="security">Security</a>
<router-outlet />
`,
})
export class Settings {}
export const routes: Routes = [
{ path: '', redirectTo: 'settings/profile', pathMatch: 'full' },
{
path: 'settings',
component: Settings,
children: [
{ path: 'profile', component: Profile },
{ path: 'security', component: Security },
],
},
];
@Component({
selector: 'app-root',
imports: [RouterOutlet],
template: `<b>My Account</b><router-outlet />`,
})
export class App {}
The links in Settings start without / (profile, not /profile). So the router adds them to the current path, and they open settings/profile and settings/security.
Wildcard Routes
A wildcard route has the path '**'. It matches every URL. Use it to display a "Page not found" screen when no other route matches the URL.
export const routes: Routes = [
{ path: '', component: Home },
{ path: 'products', component: Products },
{ path: '**', component: NotFound }, // always last
];
In the next example, click Old promo link. No route has the path old-promo, so the wildcard route displays NotFound.
A page-not-found route
import { Component } from '@angular/core';
import { Routes, RouterOutlet, RouterLink } from '@angular/router';
@Component({ selector: 'app-home', template: `<p>Home page</p>` })
export class Home {}
@Component({
selector: 'app-not-found',
imports: [RouterLink],
template: `<p>404: this page does not exist. <a routerLink="/">Go home</a></p>`,
})
export class NotFound {}
export const routes: Routes = [
{ path: '', component: Home },
{ path: '**', component: NotFound },
];
@Component({
selector: 'app-root',
imports: [RouterOutlet, RouterLink],
template: `
<a routerLink="/">Home</a> | <a routerLink="/old-promo">Old promo link</a>
<router-outlet />
`,
})
export class App {}
Always put the '**' route last. The router uses the first route that matches. If '**' is first, it matches every URL, and the router never uses the routes below it.
Lazy-Loaded Routes
Normally, the browser downloads the code of every component when the app starts. A big app then starts slowly.
With lazy loading, the code of a page is in a separate file. The browser downloads this file only when the user opens the page for the first time. So the app starts faster.
loadComponent: Lazy-Load One Page
Use loadComponent instead of component. Its function uses import() to download the file:
export const routes: Routes = [
{ path: '', component: Home }, // downloaded when the app starts
{
path: 'reports',
loadComponent: () => import('./reports/reports').then(m => m.Reports),
},
];
loadChildren: Lazy-Load a Group of Pages
To lazy-load a part of the app that has its own child routes, use loadChildren:
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES),
}
When to Use Lazy Loading
- Use it for big pages and pages that few users open, such as reports, admin pages and settings.
- Do not use it for the home page and the pages that most users open first.
- Limitation: the first time the user opens a lazy page, the user waits while its file downloads.
The practice blocks have only one file, app.ts, so this section shows lazy loading as code only. In a CLI project, ng build puts each lazy page in its own file.
Common Mistakes
| Mistake | What you see | Fix |
|---|---|---|
No <router-outlet /> in the template | The URL changes, but no page is displayed | Add <router-outlet /> to the parent template |
Using href instead of routerLink | The whole app reloads on every click | Use routerLink |
/ at the start of a path: path: '/products' | Error: the path cannot start with a slash | Write path: 'products' |
The '**' route is first | Every URL displays "Page not found" | Put the '**' route last |
Empty-path redirect without pathMatch: 'full' | The redirect runs on every page, or the wrong page opens | Add pathMatch: 'full' |
No withComponentInputBinding() in provideRouter() | The @Input() property for the parameter stays empty | Add withComponentInputBinding() |
@Input() name is different from the parameter name | The property stays empty | Use the same name: :id needs @Input() id |
Check Yourself
Exercises
What to Remember
- A route connects a URL path to a component.
provideRouter(routes)turns on the router,<router-outlet />displays the component, androuterLinkorRouter.navigate()opens a route. :idin a path is a route parameter, and?name=valueadds query parameters. Read both with@Input()properties that have the same names.- Use
redirectTowithpathMatch: 'full'for the empty URL, andchildrenfor pages inside pages. Put the'**'route last for "Page not found", and useloadComponentto download a page only when the user opens it.
Next: RxJS Fundamentals, how Observables send values over time and how operators change those values.