Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Route parameters and navigation

A route parameter is a variable part of the address. A post page does not need a hundred routes: one route posts/:id matches /posts/1, /posts/2 and so on, and the component receives the post id and loads the post.

A route with a parameter

ng generate component post-detail
// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { PostList } from './post-list/post-list';
import { PostDetail } from './post-detail/post-detail';
 
export const routes: Routes = [
  { path: 'posts', component: PostList },
  { path: 'posts/:id', component: PostDetail },
];

The colon marks a parameter named id.

In the post list the title becomes a link. An array in routerLink builds the address from parts:

<!-- src/app/post-list/post-list.html -->
@for (post of posts(); track post.id) {
  <h3><a [routerLink]="['/posts', post.id]">{{ post.title }}</a></h3>
}

Add RouterLink to the imports of the PostList component. The links lead to /posts/1, /posts/2 and so on.

The parameter as an input

The simplest way to get a parameter is to enable binding parameters to inputs in app.config.ts:

// src/app/app.config.ts
provideRouter(routes, withComponentInputBinding()),

Import withComponentInputBinding from @angular/router. Now the id parameter goes into the input with the same name:

// src/app/post-detail/post-detail.ts
import { Component, OnInit, inject, input, signal } from '@angular/core';
import { RouterLink } from '@angular/router';
import { PostApi } from '../post-api';
import { Post } from '../post';
 
@Component({
  selector: 'app-post-detail',
  imports: [RouterLink],
  templateUrl: './post-detail.html',
})
export class PostDetail implements OnInit {
  private api = inject(PostApi);
  id = input.required<string>();
  post = signal<Post | null>(null);
 
  ngOnInit() {
    this.api.getPost(Number(this.id())).subscribe((p) => this.post.set(p));
  }
}
<!-- src/app/post-detail/post-detail.html -->
<a routerLink="/posts">← All posts</a>
@if (post(); as p) {
  <h2>{{ p.title }}</h2>
  <p>{{ p.body }}</p>
} @else {
  <p>Loading...</p>
}

Click a title in the list — the post page opens. Parameters from the address are always strings, so Number() turns id into a number.

With ActivatedRoute

Without withComponentInputBinding the parameter is read from the ActivatedRoute service:

// src/app/post-detail/post-detail.ts
private route = inject(ActivatedRoute);
 
ngOnInit() {
  const id = Number(this.route.snapshot.paramMap.get('id'));
  this.api.getPost(id).subscribe((p) => this.post.set(p));
}

Many older examples are written this way — the result is the same.

The Router service moves to another page from a method — for example, after a form is saved:

// src/app/post-form/post-form.ts
private router = inject(Router);
 
save(title: string, body: string) {
  this.api.createPost({ title, body }).subscribe(() => {
    this.router.navigate(['/posts']);
  });
}

The navigate() method takes the same array as routerLink, for example ['/posts', 5]. There is also navigateByUrl('/posts') for a ready-made string.

Common mistake

When you go from /posts/1 to /posts/2, Angular does not create the component again but reuses it — ngOnInit is not called a second time, and the old post stays on the page. If such transitions are possible (for example, a "Next post" link), load the data in an effect() that reads this.id(): it runs on every new parameter value.

Thanks for reading!

Donate to the site — we work hard for you.