Angular Routing Essentials

Imagine you're building a blog. You want your readers to click on a post and instantly read it without the whole page reloading. You want them to bookmark posts, share URLs, and come back to the exact article later. This is where routing comes in—it's not just about moving between pages, it's about building navigation that scales.

In this article, we’ll break down the essentials of Angular routing with real examples. If you're new to Angular, this is your on-ramp.

Routing in Modern Web Apps

Routing is what gives SPAs (Single Page Applications) their magic. With traditional websites, each link means a new request to the server and a page refresh. SPAs keep users on the same page while swapping content dynamically.

Angular’s router handles this by mapping URLs to components. This helps:

  • Manage navigation: Seamless transitions between views.
  • Preserve state: The app remembers where the user was.
  • Improve shareability: URLs map directly to content.

The router in Angular is powerful—but it’s also learnable. Let’s build a simple blog app to see it in action.

Your First Router

Our blog app will have three routes:

  • Homepage (/) – Welcomes the user
  • Article List (/posts) – Lists blog posts
  • Single Article (/posts/:slug) – Displays an individual post

Let’s break this down step-by-step.

Step 1: Define Routes

We start with a Routes array. Each route maps a path to a component:

// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { PostListComponent } from './posts/post-list.component';
import { PostDetailComponent } from './posts/post-detail.component';

export const routes: Routes = [
  { path: '', component: HomeComponent, title: 'Home' },
  {
    path: 'posts',
    children: [
      { path: '', component: PostListComponent, title: 'All Posts' },
      { path: ':slug', component: PostDetailComponent, title: 'Post Details' }
    ]
  },
  { path: '**', redirectTo: '' } // Fallback route
];

The children array shows how nested routes work. /posts loads the list. /posts/angular-routing loads a specific article.

Step 2: Configure the Router

The router needs to be set up in your app’s configuration. Angular uses the provideRouter function to register routes and features.

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding, withViewTransitions } from '@angular/router';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      routes,
      withComponentInputBinding(),
      withViewTransitions()
    ),
  ]
};

Notice how we’re enabling route param binding (withComponentInputBinding) and transitions (withViewTransitions) to make navigation smoother.

Step 3: Add Router Outlet

Now we tell Angular where to render the routed components:

// app.component.ts
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="/posts" routerLinkActive="active">Posts</a>
    </nav>

    <router-outlet />
  `,
  styles: [`
    .active {
      font-weight: bold;
      color: #3f51b5;
    }
  `]
})
export class AppComponent {}

The <router-outlet> is where Angular renders the matching route’s component. It’s like a placeholder that gets swapped in and out.

Core Routing Concepts

Dynamic Route Parameters

When a user visits /posts/angular-routing, we want to know which post to load. That’s where route parameters come in:

// post-detail.component.ts
import { Component, input, inject, signal, effect } from '@angular/core';
import { PostService } from './post.service';
import { CommonModule } from '@angular/common';
import { Post } from './post.model';

@Component({
  selector: 'app-post-detail',
  standalone: true,
  imports: [CommonModule],
  template: `
    @if (post()) {
      <div>
        <h1>{{ post().title }}</h1>
        <div>{{ post().content }}</div>
      </div>
    } @else {
      <p>Loading post...</p>
    }
  `
})
export class PostDetailComponent {
  private postService = inject(PostService);

  slug = input<string>('');
  post = signal<Post | null>(null);

  constructor() {
    effect(() => {
      const slugValue = this.slug();
      if (slugValue) {
        this.postService.getPost(slugValue).then(this.post.set);
      }
    });
  }
}

With input(), the slug is passed automatically from the route.

Reactive Routing with Signals

Imagine you're building a navigation bar that highlights the current route, or a feature that changes layout based on where the user is in the app. Instead of subscribing to Router.events and manually updating state, signals let you express this in a reactive, declarative way — like computed state in a spreadsheet.

Angular 16+ turns routing into a reactive stream by default — especially when you combine it with effect() and signal().

Let’s say you want to reactively track the current URL so you can highlight a menu item, animate transitions, or tweak layout.

Here’s how to do it:

// navbar.component.ts
import { Component, signal, effect, inject } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { CommonModule } from '@angular/common';
import { filter } from 'rxjs/operators';

@Component({
  selector: 'app-navbar',
  standalone: true,
  imports: [CommonModule],
  template: `
    <nav>
      <a [class.active]="currentUrl() === '/'" routerLink="/">Home</a>
      <a [class.active]="currentUrl().startsWith('/posts')" routerLink="/posts">Posts</a>
    </nav>
  `,
  styles: [`
    .active {
      font-weight: bold;
      color: #3f51b5;
    }
  `]
})
export class NavbarComponent {
  private router = inject(Router);

  // Holds the current URL as a reactive signal
  currentUrl = signal(this.router.url);

  constructor() {
    // Watch for route changes and update the signal
    this.router.events.pipe(
      filter(e => e instanceof NavigationEnd)
    ).subscribe((event: NavigationEnd) => {
      this.currentUrl.set(event.urlAfterRedirects);
    });
  }
}

We create a signal<string> that holds the current route.

We use Router.events to listen for NavigationEnd — this tells us the user has completed a navigation.

When a new route is hit, we update the signal with set(...).

The template uses currentUrl() directly to reactively bind the UI — no manual DOM fiddling, no extra change detection needed.

Now your UI can respond to route changes reactively.

Pro Tips for Production-Grade Routing

  • Use nested routes to match the structure of your UI
  • Leverage signals to make routing reactive and clean
  • Fallback routes keep your app from breaking on unknown URLs
  • Dynamic titles help with SEO and tab management

What’s Next in the Series?

This was the foundation. Next, we’ll cover:

  • Lazy loading for performance
  • Route guards for protected pages
  • Preloading strategies
  • Transition animations
  • Micro-frontend routing patterns

You’ve now got a solid grip on Angular routing. In the next part, we’ll crank it up a notch and add smarter navigation flows.

Key Takeaways

  • Routing lets you build apps that feel fast and behave predictably
  • Angular’s router is declarative, powerful, and plays well with signals
  • You only need a few core concepts to get started—and they scale with your app

Stay tuned!

Comments