Skip to content
Angular ng routing 4 min read

Introduction to Routing

Most non-trivial Angular applications are single-page applications (SPAs): the browser loads one HTML shell, and from then on the framework swaps views in and out without full-page reloads. The Angular Router is the library that makes this possible — it maps URL paths to components, keeps the browser’s address bar in sync, manages browser history, and provides hooks for guarding, resolving, and lazy-loading parts of your app. Getting routing right is foundational, because it shapes how your features are organized and how users move through them.

What the Router does

At its core the Router maintains a mapping between a URL and a tree of components to display. When the URL changes — whether from a link click, a back button, or programmatic navigation — the Router matches the new path against your route configuration, activates the matching components, and renders them into placeholders called router outlets. Because everything happens on the client, navigation is fast and state can be preserved between views.

The Router gives you:

CapabilityDescription
Declarative configA Routes array maps paths to components.
Outlets<router-outlet> marks where matched components render.
LinksrouterLink builds and navigates to URLs declaratively.
ParametersPath, query, and fragment values are exposed as observables/signals.
Guards & resolversFunctional hooks to allow/block navigation and prefetch data.
Lazy loadingLoad route code on demand to shrink the initial bundle.

Enabling the Router in a standalone app

Modern Angular (17+) apps are bootstrapped without NgModule. You register the Router by adding provideRouter() to the application providers and passing it your route definitions. Keep the routes in their own file so they stay easy to read as the app grows.

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

export const routes: Routes = [
  { path: '', component: HomeComponent, title: 'Home' },
  { path: 'about', component: AboutComponent, title: 'About us' },
  { path: '**', redirectTo: '' },
];
// 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)],
};
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

bootstrapApplication(AppComponent, appConfig)
  .catch((err) => console.error(err));

The title property is set automatically as the document title when each route activates — a small built-in feature that saves you from wiring up Title manually.

The provideRouter() function replaces the older RouterModule.forRoot(). You can extend it with features such as withComponentInputBinding(), withViewTransitions(), and withInMemoryScrolling() — each is a tree-shakable, opt-in piece of behavior.

Rendering routed components

The Router needs somewhere to draw the matched component. That placeholder is <router-outlet>, which you place in your root component’s template. Because standalone components import their own dependencies, remember to import RouterOutlet (and RouterLink if you use links) directly.

// app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink } from '@angular/router';

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

When you visit /about, the Router matches the second route and renders AboutComponent inside the outlet; the <nav> stays untouched. Clicking the links updates the URL and view without a network round-trip to the server.

Reacting to navigation

Sometimes you need to know when navigation happens — for analytics, scroll handling, or loading indicators. Inject the Router and observe its event stream. You can also read the current URL at any time.

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

@Component({
  selector: 'app-shell',
  standalone: true,
  template: `<router-outlet />`,
})
export class ShellComponent {
  private router = inject(Router);

  constructor() {
    this.router.events
      .pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))
      .subscribe((e) => console.log('Navigated to', e.urlAfterRedirects));
  }
}

Output:

Navigated to /
Navigated to /about

Best practices

  • Keep route definitions in a dedicated app.routes.ts file and feature-level *.routes.ts files rather than inlining them.
  • Always include a wildcard route (path: '**') to handle unknown URLs with a redirect or a 404 component.
  • Prefer routerLink over hardcoded <a href> so navigation stays within the SPA and history works correctly.
  • Set a title on each route for accessible, descriptive document titles out of the box.
  • Use functional features like withComponentInputBinding() to bind route params straight to component inputs, keeping components lean.
  • Order matters: more specific paths must come before less specific ones, and the wildcard route must always be last.
Last updated June 14, 2026
Was this helpful?