| |

Angular 22 🅰️ Routing Basics

A single-page application doesn’t have real pages — it swaps views without reloading. Routing is what makes that work. The Angular Router maps URLs to components, renders the matched component in a <router-outlet>, listens for navigation, and keeps the browser’s history in sync. It’s how /users shows the user list, /users/42 shows one user, and /settings shows the settings page — all in the same tab, without a full reload. This chapter covers the basics: routes, outlets, routerLink, programmatic navigation, and the pieces you need for a real app.

Key point: Routing in Angular has three pieces: a route configuration that maps URLs to components, a <router-outlet> where the matched component renders, and navigation — either declarative ([routerLink]) or programmatic (router.navigate()). Standalone apps provide routing with provideRouter(routes). The router reads the current URL, finds the matching route, and renders the component in the outlet. Everything else — parameters, guards, lazy loading — builds on this foundation.


What routing is

Routing maps URLs to components. When the URL changes, the router picks the matching component and renders it.

import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent }
];

Two routes:

  • /HomeComponent
  • /aboutAboutComponent

Navigate to /about, and Angular renders AboutComponent in the outlet.

The pieces:

PiecePurpose
RoutesArray of route definitions
provideRouter(routes)Registers routes with the app
<router-outlet>Placeholder where matched components render
RouterLinkDirective for declarative navigation
RouterService for programmatic navigation

What the router does:

  • Reads the current URL
  • Matches it against the route config
  • Renders the matching component in the outlet
  • Listens for link clicks and navigate() calls
  • Updates the URL when navigation happens
  • Manages browser history (back/forward)

What it doesn’t do:

  • Doesn’t reload the page
  • Doesn’t fetch data by itself (that’s resolvers)
  • Doesn’t decide what’s public (that’s guards)
  • Doesn’t optimize loading (that’s lazy loading)

Why routing matters: Without it, every navigation is a full page load. With it, the app stays loaded, state is preserved, and navigation is instant. That’s what makes a SPA feel like an app instead of a website.

Why the router is a separate module: It’s not required for every app. A simple component demo doesn’t need routing. But any app with multiple views does. Keeping the router separate lets you add it when you need it — and keeps the minimal app minimal.


Setting up routing

For a standalone app, routing is provided with provideRouter.

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)]
});

provideRouter(routes) registers the routes with the app. Every component can now use routing features.

The root component hosts the outlet:

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `
    <header>
      <nav>
        <a routerLink="/">Home</a>
        <a routerLink="/about">About</a>
      </nav>
    </header>
    <main>
      <router-outlet></router-outlet>
    </main>
  `
})
export class AppComponent {}

The <router-outlet> is where the matched component renders. When the route changes, Angular swaps the component inside the outlet.

Import RouterOutlet and RouterLink: In standalone components, import the directives and services you use.

import { RouterOutlet, RouterLink } from '@angular/router';

@Component({
  standalone: true,
  imports: [RouterOutlet, RouterLink],
  // ...
})

The route file:

// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent }
];

A Routes array is just a list of { path, component } objects. The path is the URL segment; the component is what renders.

Why provideRouter: It’s the standalone replacement for RouterModule.forRoot(). It registers the router, the routes, and the directives with the app. Modern apps use it instead of importing RouterModule in a module.

Why the outlet is separate from the routes: The outlet is a placeholder — it appears once in the template, and the router fills it. If the outlet were tied to a route, it couldn’t be reused. Keeping it separate means one outlet can render many routes over time.


Route configuration

Routes are defined as an array of objects. Each object is a route.

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'contact', component: ContactComponent },
  { path: '**', component: NotFoundComponent }
];

Route properties:

PropertyPurpose
pathURL segment to match
componentComponent to render
redirectToRedirect to another path
pathMatchHow to match the path
childrenNested routes
loadComponentLazy-load a standalone component
loadChildrenLazy-load a module
canActivateGuards
resolveResolvers
dataStatic data

Path matching:

  • path: '' — matches the empty path (root)
  • path: 'about' — matches /about
  • path: 'users/:id' — matches /users/42 with a param
  • path: '**' — wildcard, matches anything

The wildcard route: Should be last. It catches unmatched URLs.

{ path: '**', component: NotFoundComponent }

If it came first, everything would match it.

The empty path — path: '': Matches the root URL.

{ path: '', component: HomeComponent }

pathMatch: 'full': For the empty path, use 'full' to avoid matching every URL.

{ path: '', component: HomeComponent, pathMatch: 'full' }

Without 'full', path: '' would match any URL that starts with the empty string — which is all of them.

Redirects:

{ path: 'home', redirectTo: '', pathMatch: 'full' },
{ path: 'old-about', redirectTo: 'about' }

Navigating to /home redirects to /. Navigating to /old-about redirects to /about.

Lazy loading a standalone component:

{
  path: 'settings',
  loadComponent: () => import('./settings.component').then(m => m.SettingsComponent)
}

The component loads only when the route is activated.

Lazy loading a route group:

{
  path: 'admin',
  loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes)
}

Loads a set of child routes on demand.

Why route order matters: Angular matches routes in order. The first match wins. Put specific routes first, wildcards last. Otherwise, a general route could shadow a specific one.

Why pathMatch: 'full': By default, path: '' matches any URL starting with the empty string — effectively everything. pathMatch: 'full' requires an exact match of the entire URL. It’s almost always what you want for path: ''.


<router-outlet> — where components render

The outlet is a placeholder in the template where the router renders the matched component.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `
    <main>
      <router-outlet></router-outlet>
    </main>
  `
})
export class AppComponent {}

When the URL is /about, AboutComponent renders inside the outlet. When the URL is /, HomeComponent renders.

Multiple outlets: You can have more than one outlet by naming them.

<router-outlet></router-outlet>          <!-- primary -->
<router-outlet name="sidebar"></router-outlet>  <!-- named -->

Named outlets — routes can target a specific outlet.

{
  path: 'chat',
  component: ChatComponent,
  outlet: 'sidebar'
}

Navigate to /(sidebar:chat) and ChatComponent renders in the sidebar outlet while the primary outlet shows the main content.

Nested outlets: With child routes, a child component can have its own outlet.

// Parent template
<router-outlet></router-outlet>  <!-- renders child routes -->

// Routes
{
  path: 'users',
  component: UsersComponent,
  children: [
    { path: ':id', component: UserDetailComponent }
  ]
}

Visiting /users/42 renders UsersComponent in the root outlet, and UserDetailComponent in the UsersComponent‘s outlet.

How the outlet knows what to render: It subscribes to the router’s state. When the URL matches a route, the router emits the matched component. The outlet renders it.

Why the outlet is a directive: It’s imported like any other directive. In standalone components, you add RouterOutlet to imports. It registers itself with the router, and the router fills it.

Why “outlet”: It’s where the routed content comes out. Like a wall outlet provides power, the router outlet provides a place for the routed component. The name is metaphorical but apt.


[routerLink] — declarative navigation

[routerLink] creates a link that navigates without a full page reload.

<a routerLink="/">Home</a>
<a routerLink="/about">About</a>
<a [routerLink]="['/users', userId]">Profile</a>

Static path:

<a routerLink="/about">About</a>

Dynamic path with array syntax:

<a [routerLink]="['/users', userId]">User {{ userId }}</a>

The array is joined into a path: ['/users', 42]/users/42.

Relative links:

<!-- Current URL: /users/42/details -->
<a routerLink="../">Up</a>         <!-- → /users/42 -->
<a routerLink="./edit">Edit</a>    <!-- → /users/42/edit -->
<a routerLink="../../">Two up</a>  <!-- → /users -->

Relative to the current route.

Query parameters and fragments:

<a [routerLink]="['/search']" [queryParams]="{ q: 'hello' }">Search</a>
<a [routerLink]="['/docs']" fragment="section-1">Docs</a>

Renders /search?q=hello and /docs#section-1.

Active link styling: routerLinkActive adds a class when the route is active.

<a routerLink="/about" routerLinkActive="active">About</a>

The active class is added when /about is the current route.

Exact matching:

<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Home</a>

Without exact, the root link would be active on every route. With exact: true, it’s active only on /.

Why routerLink instead of href: href triggers a full page load. routerLink tells the router to navigate — no reload, state preserved, faster.

Why array syntax: It handles special characters and dynamic segments cleanly. ['/users', userId] produces the right URL regardless of what userId contains.

Why routerLinkActive: Highlighting the current page in navigation is a common need. Without it, you’d manually compare the current URL to each link. The directive does that — and handles edge cases like query params and fragments.


Programmatic navigation

Sometimes navigation happens in code — after a save, on a timeout, in response to a service.

import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({ /* ... */ })
export class UserFormComponent {
  private router = inject(Router);

  save(): void {
    this.router.navigate(['/users']);
  }
}

Router.navigate() takes an array of path segments.

navigate vs navigateByUrl:

router.navigate(['/users', 42]);         // segments → /users/42
router.navigateByUrl('/users/42');       // full URL

navigate takes an array and joins it. navigateByUrl takes a full URL string.

Relative navigation:

router.navigate(['edit'], { relativeTo: this.route });
router.navigate(['../'], { relativeTo: this.route });

relativeTo sets the base for relative navigation.

Query params and fragments:

router.navigate(['/search'], {
  queryParams: { q: 'hello' },
  fragment: 'results'
});

Replacing history:

router.navigate(['/login'], { replaceUrl: true });

replaceUrl replaces the current history entry — pressing Back won’t return to the previous page.

Navigation with state:

router.navigate(['/confirm'], {
  state: { userId: 42 }
});

State isn’t in the URL; it’s stored in the browser history entry. Retrieve it in the target component with history.state.

When to use which:

NeedUse
Link in template[routerLink]
Navigate in coderouter.navigate()
Full URL stringrouter.navigateByUrl()
Relativerouter.navigate([...], { relativeTo })
Replace history{ replaceUrl: true }
Query params{ queryParams: {...} }

Why programmatic navigation matters: Forms, buttons, and side effects often navigate as part of a flow. [routerLink] handles links; navigate() handles logic.

Return value: navigate() returns a Promise<boolean>. true if navigation succeeded, false if a guard blocked it.

const success = await this.router.navigate(['/dashboard']);
if (!success) {
  console.log('Navigation blocked');
}

Why promises and not observables: Navigation can be async (lazy loading, guards). The promise resolves when navigation completes or fails. Angular 15+ uses promises for the modern API.

Why both navigate and navigateByUrl: They handle different inputs. navigate takes segments and applies the router’s URL-building rules. navigateByUrl takes a string and parses it. navigate is safer for user input; navigateByUrl is convenient for known URLs.


Reading the current URL

The Router and ActivatedRoute services expose the current URL.

From Router:

import { Router } from '@angular/router';

export class Component {
  private router = inject(Router);

  getCurrentUrl(): string {
    return this.router.url;
  }
}

router.url is the current URL as a string.

From ActivatedRoute:

import { ActivatedRoute } from '@angular/router';

export class Component {
  private route = inject(ActivatedRoute);

  ngOnInit(): void {
    console.log(this.route.snapshot.url);
  }
}

ActivatedRoute provides the route’s segments, params, query params, fragment, and data.

The two services:

ServiceProvides
RouterThe whole router — navigate, events, config
ActivatedRouteThe current route — params, data, segments

Listening to navigation events:

export class AppComponent {
  private router = inject(Router);

  constructor() {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        console.log('Navigated to', event.url);
      }
    });
  }
}

The router emits events during navigation. NavigationEnd fires when navigation completes.

Navigation lifecycle events:

EventWhen
NavigationStartNavigation begins
RoutesRecognizedRoute matched
GuardsCheckStart / EndGuards run
ResolveStart / EndResolvers run
NavigationEndNavigation completed
NavigationCancelNavigation cancelled
NavigationErrorNavigation failed

Why reading the URL matters: Components often need to know where they are — to highlight the current section, log analytics, or react to changes. The router and ActivatedRoute provide that.

Snapshot vs observable: route.snapshot gives the current value; route.params, route.queryParams, and route.data are observables that emit on change.

// Snapshot — value at activation
const id = this.route.snapshot.paramMap.get('id');

// Observable — updates on change
this.route.paramMap.subscribe(params => {
  const id = params.get('id');
});

Use the observable when the same component may be reused with different params.

Why the difference: Navigating from /users/1 to /users/2 may reuse the same component instance. The snapshot won’t update; the observable will. Subscribe to react.

Why both snapshot and observable: Snapshot is convenient for one-time reads. Observable is necessary when params can change without the component being recreated. Both have their place.


A full example

A small app with three routes and navigation.

// ============================================
// COMPONENTS
// ============================================

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-home',
  standalone: true,
  template: `
    <h1>Welcome</h1>
    <p>This is the home page.</p>
  `
})
export class HomeComponent {}

@Component({
  selector: 'app-about',
  standalone: true,
  template: `
    <h1>About</h1>
    <p>Learn more about us.</p>
  `
})
export class AboutComponent {}

@Component({
  selector: 'app-contact',
  standalone: true,
  template: `
    <h1>Contact</h1>
    <p>Get in touch.</p>
  `
})
export class ContactComponent {}

@Component({
  selector: 'app-not-found',
  standalone: true,
  template: `
    <h1>404</h1>
    <p>Page not found.</p>
  `
})
export class NotFoundComponent {}

// ============================================
// ROUTES
// ============================================

import { Routes } from '@angular/router';

export const routes: Routes = [
  { path: '', component: HomeComponent, pathMatch: 'full' },
  { path: 'about', component: AboutComponent },
  { path: 'contact', component: ContactComponent },
  { path: '**', component: NotFoundComponent }
];

// ============================================
// ROOT COMPONENT
// ============================================

import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, RouterLink, RouterLinkActive],
  template: `
    <header>
      <nav>
        <a
          routerLink="/"
          routerLinkActive="active"
          [routerLinkActiveOptions]="{ exact: true }">
          Home
        </a>
        <a routerLink="/about" routerLinkActive="active">About</a>
        <a routerLink="/contact" routerLinkActive="active">Contact</a>
      </nav>
    </header>
    <main>
      <router-outlet></router-outlet>
    </main>
  `,
  styles: `
    nav a.active {
      font-weight: bold;
      color: blue;
    }
  `
})
export class AppComponent {}

// ============================================
// BOOTSTRAP
// ============================================

import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)]
});

What this shows:

  • Four components, each with a template
  • Routes mapping paths to components
  • pathMatch: 'full' on the root route
  • Wildcard route for 404s
  • [routerLink] for navigation
  • routerLinkActive for highlighting
  • provideRouter for bootstrap

Navigate between pages — no reload, state preserved.

Why this shape: It’s the minimal viable router app. Every piece is present: routes, outlet, links, active state, wildcard. That structure scales — nested routes, parameters, guards all build on it.


Complete Example Session

# ============================================
# PART 1: HOME COMPONENT
# ============================================

cat > home.component.ts << 'EOF'
import { Component } from '@angular/core';

@Component({
  selector: 'app-home',
  standalone: true,
  template: `<h1>Home</h1>`
})
export class HomeComponent {}
EOF

# ============================================
# PART 2: ABOUT COMPONENT
# ============================================

cat > about.component.ts << 'EOF'
import { Component } from '@angular/core';

@Component({
  selector: 'app-about',
  standalone: true,
  template: `<h1>About</h1>`
})
export class AboutComponent {}
EOF

# ============================================
# PART 3: NOT FOUND
# ============================================

cat > not-found.component.ts << 'EOF'
import { Component } from '@angular/core';

@Component({
  selector: 'app-not-found',
  standalone: true,
  template: `<h1>404</h1>`
})
export class NotFoundComponent {}
EOF

# ============================================
# PART 4: ROUTES
# ============================================

cat > app.routes.ts << 'EOF'
import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';
import { NotFoundComponent } from './not-found.component';

export const routes: Routes = [
  { path: '', component: HomeComponent, pathMatch: 'full' },
  { path: 'about', component: AboutComponent },
  { path: '**', component: NotFoundComponent }
];
EOF

npx tsc --noEmit app.routes.ts
# (no errors)

# ============================================
# PART 5: ROOT COMPONENT
# ============================================

cat > app.component.ts << 'EOF'
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, RouterLink, RouterLinkActive],
  template: `
    <nav>
      <a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Home</a>
      <a routerLink="/about" routerLinkActive="active">About</a>
    </nav>
    <router-outlet></router-outlet>
  `
})
export class AppComponent {}
EOF

npx tsc --noEmit app.component.ts
# (no errors)

# ============================================
# PART 6: BOOTSTRAP
# ============================================

cat > main.ts << 'EOF'
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app.component';
import { routes } from './app.routes';

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)]
});
EOF

npx tsc --noEmit main.ts
# (no errors)

# ============================================
# PART 7: PROGRAMMATIC NAVIGATION
# ============================================

cat > form.component.ts << 'EOF'
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-form',
  standalone: true,
  template: `<button (click)="save()">Save</button>`
})
export class FormComponent {
  private router = inject(Router);

  save(): void {
    // ... save logic
    this.router.navigate(['/about']);
  }
}
EOF

npx tsc --noEmit form.component.ts
# (no errors)

# ============================================
# PART 8: READING URL
# ============================================

cat > url.component.ts << 'EOF'
import { Component, inject } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs';

@Component({
  selector: 'app-url',
  standalone: true,
  template: `<p>{{ currentUrl }}</p>`
})
export class UrlComponent {
  private router = inject(Router);
  currentUrl = this.router.url;

  constructor() {
    this.router.events
      .pipe(filter(e => e instanceof NavigationEnd))
      .subscribe(() => {
        this.currentUrl = this.router.url;
      });
  }
}
EOF

npx tsc --noEmit url.component.ts
# (no errors)

# ============================================
# PART 9: SUMMARY
# ============================================

cat << 'EOF'
Router structure:

  main.ts
  └── provideRouter(routes)

  app.routes.ts
  └── Routes array

  app.component.ts
  ├── RouterOutlet      ← where routes render
  ├── RouterLink        ← declarative navigation
  └── RouterLinkActive  ← active state

  Feature components
  └── rendered by the outlet

Navigation:
  [routerLink]="'/path'"           → static
  [routerLink]="['/path', id]"     → dynamic
  router.navigate(['/path'])       → programmatic
  router.navigateByUrl('/path')    → full URL
EOF

Quick Reference

Setup

StepCode
Importimport { provideRouter } from '@angular/router';
Provideproviders: [provideRouter(routes)]
Define routesexport const routes: Routes = [...]
Outlet<router-outlet></router-outlet>
Link<a routerLink="/path">

Route Properties

PropertyPurpose
pathURL segment
componentComponent to render
redirectToRedirect target
pathMatch'prefix' or 'full'
childrenNested routes
loadComponentLazy-load component
loadChildrenLazy-load routes
canActivateGuard
resolveResolver
dataStatic data
outletNamed outlet

Path Matching

PathMatches
''Empty (root)
'about'/about
'users/:id'/users/42
'**'Anything (wildcard)
'a/b'/a/b (nested in path)

pathMatch

ValueMeaning
'prefix'Default — URL starts with path
'full'URL exactly equals path

routerLink Forms

FormResult
routerLink="/about"Static path
[routerLink]="['/users', id]"Dynamic segments
[routerLink]="['../']"Relative
[routerLink]="['/search']" [queryParams]="{...}"With query
[routerLink]="['/docs']" fragment="section"With fragment

routerLinkActive

UsageEffect
routerLinkActive="active"Adds class when active
[routerLinkActiveOptions]="{ exact: true }"Exact match only
[routerLinkActiveOptions]="{ exact: false }"Prefix match (default)

Programmatic Navigation

MethodInput
router.navigate(['/path'])Segments array
router.navigateByUrl('/path')Full URL
router.navigate(['../'], { relativeTo })Relative
router.navigate(['/x'], { queryParams })With query
router.navigate(['/x'], { replaceUrl: true })Replace history
router.navigate(['/x'], { state })With state

Router Services

ServiceProvides
RouterNavigation, events, URL
ActivatedRouteCurrent route info

ActivatedRoute Properties

PropertyType
snapshotCurrent route snapshot
paramsObservable of params
queryParamsObservable of query params
dataObservable of static data
urlObservable of URL segments
fragmentObservable of fragment

Snapshot vs Observable

UseWhen
SnapshotOne-time read
ObservableReacts to changes

Navigation Events

EventWhen
NavigationStartBegins
RoutesRecognizedMatched
GuardsCheckStartGuards begin
GuardsCheckEndGuards done
ResolveStartResolvers begin
ResolveEndResolvers done
NavigationEndCompleted
NavigationCancelCancelled
NavigationErrorFailed

Directives and Services

ImportPurpose
RouterOutletRender matched component
RouterLinkDeclarative navigation
RouterLinkActiveActive state
RouterProgrammatic navigation
ActivatedRouteCurrent route info
provideRouterRegister routes

Error Cases

ErrorCause
Cannot match any routesNo matching route
Cannot activate an already activated outletNested outlet config
Invalid configurationWrong route shape
NullInjectorError: RouterForgot provideRouter

Common Patterns

PatternCode
Root route{ path: '', component: Home, pathMatch: 'full' }
Wildcard{ path: '**', component: NotFound }
Redirect{ path: 'old', redirectTo: 'new', pathMatch: 'full' }
Lazy component{ path: 'x', loadComponent: () => import(...) }
Lazy routes{ path: 'x', loadChildren: () => import(...) }
Named outlet{ path: 'x', component: C, outlet: 'sidebar' }

Best Practices

Do This:

// Use provideRouter for standalone apps
providers: [provideRouter(routes)]                       // ✅

// Use pathMatch: 'full' on empty path
{ path: '', component: Home, pathMatch: 'full' }          // ✅

// Put wildcard last
{ path: '**', component: NotFound }                       // ✅

// Use routerLink in templates
<a routerLink="/about">About</a>                          // ✅

// Use array syntax for dynamic paths
<a [routerLink]="['/users', userId]">Profile</a>          // ✅

// Use routerLinkActive for current state
<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Home</a> // ✅

// Use router.navigate in code
this.router.navigate(['/users']);                         // ✅

// Use navigateByUrl for known URLs
this.router.navigateByUrl('/settings');                   // ✅

// Use replaceUrl to avoid back-button surprises
this.router.navigate(['/login'], { replaceUrl: true });   // ✅

// Subscribe to params when the component may be reused
this.route.paramMap.subscribe(params => { /* ... */ });   // ✅

Don’t Do This:

// Don't forget provideRouter
bootstrapApplication(App, { providers: [] });  // ⚠️  no routing  // ⚠️

// Don't omit pathMatch on empty path
{ path: '', component: Home }  // ⚠️  matches everything       // ⚠️

// Don't put wildcard first
{ path: '**', component: NotFound },
{ path: 'about', component: About }  // ⚠️  never reached       // ⚠️

// Don't use href for internal links
<a href="/about">About</a>  // ⚠️  full page reload            // ⚠️

// Don't forget to import RouterOutlet
@Component({ imports: [] })  // ⚠️  outlet won't work          // ⚠️

// Don't use snapshot if params change
const id = this.route.snapshot.paramMap.get('id');
// ⚠️  won't update on /users/1 → /users/2                    // ⚠️

// Don't navigate to hardcoded strings in templates
<a routerLink="/Users">  // ⚠️  case-sensitive, might not match // ⚠️

// Don't ignore navigation errors
this.router.navigate(['/x']);  // ⚠️  handle the promise        // ⚠️

Common Pitfalls

PitfallProblemSolution
Missing provideRouterNo router injectionAdd to providers
Missing pathMatchRoot matches allUse 'full'
Wildcard firstUnreachable routesMove wildcard last
href instead of routerLinkFull reloadUse routerLink
Snapshot with changing paramsStale valueSubscribe to observable
Forgot to import RouterOutletOutlet doesn’t workAdd to imports
Case mismatch in pathRoute doesn’t matchMatch exactly
Relative link confusionWrong targetUnderstand ../ and ./
Navigation in ngOnInitComponent may not be readyUse effects or guards
Multiple outlets unnamedWrong targetName them

Real-World Examples

1. Basic route

{ path: 'about', component: AboutComponent }

2. Root route

{ path: '', component: HomeComponent, pathMatch: 'full' }

3. Wildcard

{ path: '**', component: NotFoundComponent }

4. Redirect

{ path: 'old', redirectTo: 'new', pathMatch: 'full' }

5. Lazy component

{ path: 'admin', loadComponent: () => import('./admin.component').then(m => m.AdminComponent) }

6. Lazy routes

{ path: 'shop', loadChildren: () => import('./shop/shop.routes').then(m => m.shopRoutes) }

7. routerLink static

<a routerLink="/about">About</a>

8. routerLink dynamic html

<a [routerLink]="['/users', userId]">Profile</a>

9. Query params

<a [routerLink]="['/search']" [queryParams]="{ q: 'x' }">Search</a>

10. Fragment

<a [routerLink]="['/docs']" fragment="intro">Docs</a>

11. Active link

<a routerLink="/about" routerLinkActive="active">About</a>

12. Exact active match

<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Home</a>

13. Programmatic navigation

this.router.navigate(['/users']);

14. Navigate by URL

this.router.navigateByUrl('/users/42');

15. Relative navigation

this.router.navigate(['edit'], { relativeTo: this.route });

16. Navigate with state

this.router.navigate(['/confirm'], { state: { userId: 42 } });

17. Replace history

this.router.navigate(['/login'], { replaceUrl: true });

18. Read current URL

const url = this.router.url;

19. Navigate on route events

this.router.events.subscribe(e => {
  if (e instanceof NavigationEnd) console.log('navigated');
});

20. Read route params (reactive)

this.route.paramMap.subscribe(params => {
  const id = params.get('id');
});

Visual: Router Flow

┌──────────────────────────────────────────────┐
│  User clicks <a routerLink="/about">         │
│       │                                      │
│       ▼                                      │
│  Router intercepts the click                 │
│       │                                      │
│       ▼                                      │
│  Matches route config                        │
│       │                                      │
│       ▼                                      │
│  Loads component (if lazy)                   │
│       │                                      │
│       ▼                                      │
│  Renders in <router-outlet>                  │
│       │                                      │
│       ▼                                      │
│  Updates browser URL                         │
│       │                                      │
│       ▼                                      │
│  Adds history entry                          │
│                                              │
└──────────────────────────────────────────────┘

Visual: Router Setup

┌──────────────────────────────────────────────┐
│  main.ts                                     │
│  ─ bootstrapApplication(App, {               │
│      providers: [provideRouter(routes)]      │
│    })                                        │
│                                              │
└──────────────────────────────────────────────┘
                  │
                  ▼
┌──────────────────────────────────────────────┐
│  app.routes.ts                               │
│  ─ export const routes: Routes = [           │
│      { path: '', component: Home },          │
│      { path: 'about', component: About }     │
│    ]                                         │
│                                              │
└──────────────────────────────────────────────┘
                  │
                  ▼
┌──────────────────────────────────────────────┐
│  app.component.ts                            │
│  ─ imports: [RouterOutlet, RouterLink]       │
│  ─ template:                                 │
│      <a routerLink="/about">About</a>        │
│      <router-outlet></router-outlet>         │
│                                              │
└──────────────────────────────────────────────┘

Visual: Path Matching

┌──────────────────────────────────────────────┐
│  Routes:                                     │
│  1. { path: 'about', component: About }      │
│  2. { path: '', component: Home, pathMatch: 'full' }│
│  3. { path: '**', component: NotFound }      │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  URL /about                                  │
│    → matches route 1                         │
│                                              │
│  URL /                                       │
│    → doesn't match 1                         │
│    → matches 2 (full)                        │
│                                              │
│  URL /other                                  │
│    → doesn't match 1 or 2                    │
│    → matches 3 (wildcard)                    │
│                                              │
└──────────────────────────────────────────────┘

Visual: pathMatch: 'full'

┌──────────────────────────────────────────────┐
│  Without pathMatch:                          │
│                                              │
│  { path: '', component: Home }               │
│                                              │
│  URL /about                                  │
│    → '' is a prefix of '/about'              │
│    → matches Home                            │
│    → /about unreachable!                     │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  With pathMatch: 'full':                     │
│                                              │
│  { path: '', component: Home, pathMatch: 'full' }│
│                                              │
│  URL /about                                  │
│    → '' doesn't fully match '/about'         │
│    → doesn't match Home                      │
│                                              │
│  URL /                                       │
│    → '' fully matches '/'                    │
│    → matches Home                            │
│                                              │
└──────────────────────────────────────────────┘

Visual: RouterLink Forms

┌──────────────────────────────────────────────┐
│  Static:                                     │
│  <a routerLink="/about">                     │
│  → /about                                    │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Dynamic:                                    │
│  <a [routerLink]="['/users', 42]">           │
│  → /users/42                                 │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Relative:                                   │
│  <a routerLink="../">                        │
│  → one level up                              │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  With query:                                 │
│  <a [routerLink]="['/search']" [queryParams]="{q:'x'}">│
│  → /search?q=x                               │
│                                              │
└──────────────────────────────────────────────┘

Visual: Active Link

┌──────────────────────────────────────────────┐
│  Navigation:                                 │
│                                              │
│  <a routerLink="/" routerLinkActive="active" │
│     [routerLinkActiveOptions]="{ exact: true }">Home</a>│
│  <a routerLink="/about" routerLinkActive="active">About</a>│
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  URL = /                                     │
│    Home link → has class "active"            │
│    About link → no class                     │
│                                              │
│  URL = /about                                │
│    Home link → no class (exact: true)        │
│    About link → has class "active"           │
│                                              │
└──────────────────────────────────────────────┘

Visual: Outlet Rendering

┌──────────────────────────────────────────────┐
│  App template:                               │
│                                              │
│  <nav>...</nav>                              │
│  <router-outlet></router-outlet>             │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  URL = /                                     │
│                                              │
│  <nav>...</nav>                              │
│  <app-home></app-home>   ← rendered          │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  URL = /about                                │
│                                              │
│  <nav>...</nav>                              │
│  <app-about></app-about> ← swapped           │
│                                              │
└──────────────────────────────────────────────┘

Visual: Programmatic Navigation

┌──────────────────────────────────────────────┐
│  export class FormComponent {                │
│    private router = inject(Router);          │
│                                              │
│    save() {                                  │
│      // logic                                │
│      this.router.navigate(['/users']);       │
│    }                                         │
│  }                                           │
│                                              │
└──────────────────────────────────────────────┘
                  │
                  ▼
┌──────────────────────────────────────────────┐
│  Router parses ['/users']                    │
│  Builds URL /users                           │
│  Matches route                               │
│  Renders component                           │
│                                              │
└──────────────────────────────────────────────┘

Visual: Navigation Events

┌──────────────────────────────────────────────┐
│  NavigationStart                             │
│       │                                      │
│       ▼                                      │
│  RoutesRecognized                            │
│       │                                      │
│       ▼                                      │
│  GuardsCheckStart                            │
│  GuardsCheckEnd                              │
│       │                                      │
│       ▼                                      │
│  ResolveStart                                │
│  ResolveEnd                                  │
│       │                                      │
│       ▼                                      │
│  NavigationEnd ✅                            │
│                                              │
│  Or NavigationCancel / NavigationError       │
│                                              │
└──────────────────────────────────────────────┘

Visual: Reading Params

┌──────────────────────────────────────────────┐
│  Route: { path: 'users/:id' }                │
│                                              │
│  URL: /users/42                              │
│                                              │
│  ActivatedRoute:                             │
│  ├── snapshot.paramMap.get('id')  → '42'    │
│  └── paramMap.subscribe(...)      → emits  │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  URL: /users/42 → /users/43                  │
│                                              │
│  Component may be reused                     │
│                                              │
│  snapshot stays '42'                         │
│  observable emits '43' ✅                    │
│                                              │
└──────────────────────────────────────────────┘

Visual: Decision Flow

┌──────────────────────────────────────────────┐
│  Setting up routing?                         │
│       └── provideRouter(routes)              │
│                                              │
│  Defining a route?                           │
│       └── { path, component }                │
│                                              │
│  Root route?                                 │
│       └── pathMatch: 'full'                  │
│                                              │
│  Unknown URL?                                │
│       └── { path: '**', component: NotFound }│
│                                              │
│  Link in template?                           │
│       └── routerLink                         │
│                                              │
│  Highlight current?                          │
│       └── routerLinkActive                   │
│                                              │
│  Navigate in code?                           │
│       └── router.navigate()                  │
│                                              │
│  Read params?                                │
│       ├── Once ──► snapshot                  │
│       └── Ongoing ──► observable             │
│                                              │
└──────────────────────────────────────────────┘

Summary

ConceptMeaning
RoutingMaps URLs to components
RoutesArray of route definitions
provideRouterRegisters routes
<router-outlet>Where components render
routerLinkDeclarative navigation
routerLinkActiveActive link styling
Router.navigateProgrammatic navigation
ActivatedRouteCurrent route info
pathMatch: 'full'Exact path matching
Wildcard route** catches unmatched URLs

Key takeaways:

  • Routing maps URLs to components, swapping views without reloading
  • provideRouter(routes) registers routing in standalone apps
  • Routes is an array of { path, component } definitions
  • <router-outlet> marks where the matched component renders
  • routerLink navigates without a full page reload — prefer it over href
  • routerLinkActive adds a class when a link’s route is active
  • Use pathMatch: 'full' on the root route to prevent it matching everything
  • Put the wildcard route (**) last to catch unmatched URLs
  • Redirects{ path: 'old', redirectTo: 'new' } — remap old URLs
  • Lazy loadingloadComponent and loadChildren — loads routes on demand
  • Programmatic navigationrouter.navigate([...]) — for code-driven flows
  • Relative navigation{ relativeTo: this.route } — for nested paths
  • ActivatedRoute gives params, query params, and data; Router gives the whole state
  • Use snapshot for one-time reads; observable when params can change
  • Navigation eventsNavigationEnd, NavigationCancel, NavigationError — let you react to routing

Remember: Routing is what turns a page into an app. Routes map URLs to components. The outlet renders them. routerLink navigates without reloading. router.navigate() does it from code. Get the basics right — routes, outlet, links, active state — and the rest (parameters, guards, lazy loading) builds on top. Every Angular app with more than one view needs routing, and these are the pieces that make it work.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!