A real app has more than one page. The Angular router watches the URL and shows the matching component, without reloading the browser. In this lesson you build a small site with Home, About and a not-found page.
What you will learn
- How to define routes in
app.routes.ts - How
RouterOutletandRouterLinkwork together - How to mark the active link and handle unknown URLs
Create the pages
// src/app/pages/home.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-home',
template: `<h2>Home</h2><p>Welcome to the learning site.</p>`,
})
export class Home {}
// src/app/pages/about.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-about',
template: `<h2>About</h2><p>This site teaches Angular step by step.</p>`,
})
export class About {}
// src/app/pages/not-found.ts
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-not-found',
imports: [RouterLink],
template: `<h2>Page not found</h2><p><a routerLink="/">Go back home</a></p>`,
})
export class NotFound {}
Define the routes
// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { Home } from './pages/home';
import { About } from './pages/about';
import { NotFound } from './pages/not-found';
export const routes: Routes = [
{ path: '', component: Home, title: 'Home' },
{ path: 'about', component: About, title: 'About' },
{ path: '**', component: NotFound, title: 'Not found' },
];
The router checks routes from top to bottom and uses the first match. That is why the ** wildcard route goes last. The title property sets the browser tab title for you.
Show the pages
// src/app/app.ts
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
@Component({
imports: [RouterOutlet, RouterLink, RouterLinkActive],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<header>
<nav aria-label="Main">
<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Home</a>
<a routerLink="/about" routerLinkActive="active">About</a>
</nav>
</header>
<main>
<router-outlet />
</main>
/* src/app/app.css */
nav { display: flex; gap: 1rem; padding: 1rem 0; }
nav a.active { font-weight: bold; text-decoration: underline; }
The app config already contains provideRouter(routes) from the project the CLI generated, so no extra setup is needed.
The key parts
<router-outlet />is the placeholder where the matched page appears.routerLinkcreates a link that changes the URL without a page reload. Use it instead ofhreffor pages inside your app.routerLinkActive="active"adds a CSS class to the link of the current page.exact: trueon the Home link stops it from being active on every URL, because/is the start of all of them.provideRouter(routes)registers the routes for the whole app.
Try it yourself
- Add a
/contactpage with its own link in the navigation. - Visit a URL that does not exist and check that the not-found page appears.
- Add a redirect route
{ path: 'home', redirectTo: '', pathMatch: 'full' }and test it.
Quick check
- What does
RouterOutletdo? - Why is the
**route placed last? - Why use
routerLinkrather thanhref?
Next, learn how to read values from the URL in route parameters. The official routing guide has more detail.