The ActivatedRoute service provides access to all route information — parameters, query parameters, fragment, and resolved data. For components that do not use withComponentInputBinding(), or that need to react to parameter changes without full component recreation, ActivatedRoute remains essential. Router events enable application-level logic — showing a loading indicator during navigation, tracking page views for analytics, or logging navigation errors.
ActivatedRoute and Router Events
import { ActivatedRoute, Router, NavigationStart, NavigationEnd,
NavigationError } from '@angular/router';
// ── Reading route parameters ───────────────────────────────────────────────
@Component({ standalone: true, template: '...' })
export class PostDetailComponent implements OnInit, OnDestroy {
private route = inject(ActivatedRoute);
private api = inject(PostsApiService);
private destroyRef = inject(DestroyRef);
post = signal<PostDto | null>(null);
ngOnInit() {
// Observable approach — reacts when :slug changes without full navigation
// (useful when navigating between posts with prev/next buttons)
this.route.paramMap
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(params => {
const slug = params.get('slug')!;
this.api.getBySlug(slug).subscribe(post => this.post.set(post));
});
// Snapshot approach — reads current value once (simpler for initial load)
const slug = this.route.snapshot.paramMap.get('slug')!;
}
}
// ── Query parameters — filter/pagination state in the URL ─────────────────
@Component({ standalone: true, template: '...' })
export class PostListComponent implements OnInit {
private route = inject(ActivatedRoute);
private router = inject(Router);
searchQuery = signal('');
currentPage = signal(1);
ngOnInit() {
// Read initial query params from URL (supports bookmarking/sharing)
this.route.queryParamMap
.pipe(takeUntilDestroyed())
.subscribe(params => {
this.searchQuery.set(params.get('search') ?? '');
this.currentPage.set(Number(params.get('page') ?? 1));
});
}
// Persist filter changes to URL (bookmarkable, shareable)
onSearch(query: string): void {
this.router.navigate([], { // [] = stay on current route
relativeTo: this.route,
queryParams: { search: query || null, page: 1 },
queryParamsHandling: 'merge', // preserve other existing query params
});
}
}
// ── Navigation loading indicator ──────────────────────────────────────────
@Injectable({ providedIn: 'root' })
export class NavigationLoadingService {
private _navigating = signal(false);
readonly navigating = this._navigating.asReadonly();
constructor() {
const router = inject(Router);
router.events
.pipe(takeUntilDestroyed())
.subscribe(event => {
if (event instanceof NavigationStart) this._navigating.set(true);
if (event instanceof NavigationEnd) this._navigating.set(false);
if (event instanceof NavigationError) this._navigating.set(false);
});
}
}
// Usage: <app-progress-bar *ngIf="navLoading.navigating()" />
route.queryParams (Observable) to persist filter and pagination state in the URL. When a user bookmarks /posts?search=dotnet&page=2 or shares the URL, the page loads with the correct filter applied. Without URL-based state, filter changes only exist in memory — refreshing the page resets them. The router’s queryParamsHandling: 'merge' option preserves existing query params when updating only some — crucial for pagination (changing page should not lose the current search query).route.snapshot for one-time reads (in ngOnInit) and route.paramMap Observable for reactive reads (when the parameter might change while the component is still active, like navigating between posts with prev/next buttons on the same route). The snapshot is synchronous and simpler; the Observable is necessary only when the route parameters change without the component being destroyed and recreated.NavigationExtras.state passes data through the browser’s history state — it is not in the URL and is lost on page refresh. Use it only for transient navigation data (like passing a success message from a form submission to the next page). For data that must survive refresh or be shareable, put it in the URL (query params or route params) or in a service. Never rely on history state for critical application logic.Common Mistakes
Mistake 1 — Using snapshot for parameters that change without navigation (misses updates)
❌ Wrong — route.snapshot.paramMap.get('slug') inside ngOnInit; prev/next navigation doesn’t reload data.
✅ Correct — use route.paramMap Observable for parameters that can change while the component is active.
Mistake 2 — Not using queryParamsHandling: ‘merge’ when updating one query param
❌ Wrong — router.navigate([], { queryParams: { page: 2 } }); removes all other query params (search, sort).
✅ Correct — add queryParamsHandling: 'merge' to preserve existing params when updating only some.