Skip to main content

Angular Router Events

Introduction

The Angular Router provides a powerful event system that lets you track the lifecycle of navigation from start to finish. These events are incredibly useful for implementing loading indicators, analytics tracking, permissions validation, or any functionality that needs to respond to navigation changes.

Router events are observable sequences that you can subscribe to in order to react to different stages of the navigation process. In this tutorial, we'll explore how to use these events effectively to enhance your Angular applications.

Understanding Router Events

Angular's Router emits a sequence of events during navigation. Each event represents a specific phase in the navigation lifecycle, from the moment navigation starts to the point when it completes or fails.

Key Router Events

Here's a sequential list of the main router events:

  1. NavigationStart: Triggered when navigation starts
  2. RouteConfigLoadStart: Triggered when a route configuration starts loading (for lazy-loaded modules)
  3. RouteConfigLoadEnd: Triggered when a route configuration finishes loading
  4. RoutesRecognized: Triggered when the router recognizes the route
  5. GuardsCheckStart: Triggered when the router begins running guards
  6. ChildActivationStart: Triggered when the router begins activating a child route
  7. ActivationStart: Triggered when the router begins activating a route
  8. GuardsCheckEnd: Triggered when the router finishes running guards
  9. ResolveStart: Triggered when the router begins resolving route data
  10. ResolveEnd: Triggered when the router finishes resolving route data
  11. ChildActivationEnd: Triggered when the router finishes activating a child route
  12. ActivationEnd: Triggered when the router finishes activating a route
  13. NavigationEnd: Triggered when navigation successfully completes
  14. NavigationCancel: Triggered when navigation is canceled
  15. NavigationError: Triggered when navigation fails due to an error
  16. Scroll: Triggered when the router performs scrolling

Basic Implementation

Let's start with a basic implementation that subscribes to all router events:

typescript
import { Component, OnInit } from '@angular/core';
import { Router, Event, NavigationStart, NavigationEnd,
NavigationCancel, NavigationError } from '@angular/router';

@Component({
selector: 'app-root',
template: `
<div *ngIf="loading" class="loading-indicator">Loading...</div>
<router-outlet></router-outlet>
`,
styles: [
`.loading-indicator {
position: fixed;
top: 0;
left: 0;
right: 0;
background: #f8f9fa;
text-align: center;
padding: 10px;
border-bottom: 1px solid #ddd;
z-index: 1000;
}`
]
})
export class AppComponent implements OnInit {
loading = false;

constructor(private router: Router) {}

ngOnInit() {
this.router.events.subscribe((event: Event) => {
switch (true) {
case event instanceof NavigationStart: {
this.loading = true;
break;
}

case event instanceof NavigationEnd:
case event instanceof NavigationCancel:
case event instanceof NavigationError: {
this.loading = false;
break;
}
default: {
break;
}
}
});
}
}

In this example:

  • We subscribe to all router events
  • When NavigationStart occurs, we show a loading indicator
  • When navigation completes (NavigationEnd), gets canceled (NavigationCancel), or fails (NavigationError), we hide the loading indicator

Type-Safe Event Filtering

A more efficient approach is to filter for only the events you need:

typescript
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
import { Subscription } from 'rxjs';

@Component({
selector: 'app-navigation-tracker',
template: `<div class="navigation-status">{{status}}</div>`,
})
export class NavigationTrackerComponent implements OnInit, OnDestroy {
status = 'Idle';
private subscription: Subscription = new Subscription();

constructor(private router: Router) {}

ngOnInit() {
// Subscribe to NavigationStart events
this.subscription.add(
this.router.events.pipe(
filter(event => event instanceof NavigationStart)
).subscribe((event: NavigationStart) => {
this.status = `Navigating to ${event.url}`;
console.log('Navigation started to:', event.url);
})
);

// Subscribe to NavigationEnd events
this.subscription.add(
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe((event: NavigationEnd) => {
this.status = `Navigation complete`;
console.log('Navigation completed at:', event.url);
})
);
}

ngOnDestroy() {
// Clean up subscriptions to prevent memory leaks
this.subscription.unsubscribe();
}
}

This approach:

  • Uses RxJS filter() to listen only for specific event types
  • Makes your code cleaner and more efficient
  • Properly types each event for better IDE support and type safety
  • Properly handles subscription cleanup with OnDestroy

Practical Examples

Example 1: Progress Bar for Navigation

Let's create a more sophisticated loading indicator that shows the progress of navigation:

typescript
import { Component, OnInit } from '@angular/core';
import { Router, Event, NavigationStart, RouteConfigLoadStart,
RouteConfigLoadEnd, RoutesRecognized, GuardsCheckStart,
NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';

@Component({
selector: 'app-navigation-progress',
template: `
<div class="progress-container" *ngIf="navigationInProgress">
<div class="progress-bar" [style.width.%]="progressValue"></div>
</div>
`,
styles: [`
.progress-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background-color: #eee;
z-index: 1000;
}
.progress-bar {
height: 100%;
background-color: #3498db;
transition: width 0.2s ease;
}
`]
})
export class NavigationProgressComponent implements OnInit {
navigationInProgress = false;
progressValue = 0;

constructor(private router: Router) {}

ngOnInit() {
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationStart) {
this.navigationInProgress = true;
this.progressValue = 10;
} else if (event instanceof RouteConfigLoadStart) {
this.progressValue = 20;
} else if (event instanceof RouteConfigLoadEnd) {
this.progressValue = 40;
} else if (event instanceof RoutesRecognized) {
this.progressValue = 60;
} else if (event instanceof GuardsCheckStart) {
this.progressValue = 80;
} else if (event instanceof NavigationEnd) {
this.progressValue = 100;
setTimeout(() => {
this.navigationInProgress = false;
}, 200); // Keep progress bar visible briefly after completion
} else if (event instanceof NavigationCancel || event instanceof NavigationError) {
this.progressValue = 100;
setTimeout(() => {
this.navigationInProgress = false;
}, 200);
}
});
}
}

Example 2: Tracking and Analytics

Router events are perfect for implementing analytics tracking:

typescript
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';

@Injectable({
providedIn: 'root'
})
export class AnalyticsService {
constructor(private router: Router) {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe((event: NavigationEnd) => {
this.trackPageView(event.urlAfterRedirects);
});
}

private trackPageView(url: string) {
// Replace with your actual analytics tracking code
console.log(`Page view tracked: ${url}`);

// Example Google Analytics tracking
// if (typeof ga === 'function') {
// ga('set', 'page', url);
// ga('send', 'pageview');
// }
}
}

Then in your app.module.ts:

typescript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { AnalyticsService } from './analytics.service';

@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
RouterModule.forRoot([
// Your routes
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(private analyticsService: AnalyticsService) {
// Service will automatically start tracking when injected
}
}

Example 3: Confirmation Before Navigation

Using router events to implement a "unsaved changes" warning:

typescript
import { Component, HostListener } from '@angular/core';
import { NavigationStart, Router } from '@angular/router';
import { filter } from 'rxjs/operators';

@Component({
selector: 'app-edit-form',
template: `
<form>
<input [(ngModel)]="formData" name="data" />
<button type="submit">Save Changes</button>
</form>
`
})
export class EditFormComponent {
formData = '';
isDirty = false;

constructor(private router: Router) {
this.router.events.pipe(
filter(event => event instanceof NavigationStart)
).subscribe((event: NavigationStart) => {
if (this.isDirty && !window.confirm('You have unsaved changes. Are you sure you want to leave this page?')) {
this.router.navigate([this.router.url]);
}
});
}

@HostListener('input')
onInput() {
this.isDirty = true;
}

onSave() {
// Save data
this.isDirty = false;
}
}

Best Practices

  1. Clean up subscriptions: Always unsubscribe from router events when your component is destroyed to prevent memory leaks.

  2. Filter events: Use RxJS operators to filter only for the events you need rather than processing all events.

  3. Type safety: Make use of TypeScript's type system to work with specific router event types.

  4. Central event handling: Consider setting up a central service for handling router events if you need to react to them in multiple places.

  5. Debugging: Router events can help you debug navigation issues by logging the sequence of events.

Advanced: Creating a Router Event History Service

typescript
import { Injectable } from '@angular/core';
import { Router, Event, NavigationStart, NavigationEnd,
NavigationCancel, NavigationError } from '@angular/router';
import { BehaviorSubject } from 'rxjs';

interface RouterEventRecord {
type: string;
url?: string;
timestamp: number;
id?: number;
}

@Injectable({
providedIn: 'root'
})
export class RouterEventHistoryService {
private _history = new BehaviorSubject<RouterEventRecord[]>([]);
public history$ = this._history.asObservable();

constructor(private router: Router) {
this.router.events.subscribe((event: Event) => {
let record: RouterEventRecord | null = null;

if (event instanceof NavigationStart) {
record = {
type: 'NavigationStart',
url: event.url,
id: event.id,
timestamp: Date.now()
};
} else if (event instanceof NavigationEnd) {
record = {
type: 'NavigationEnd',
url: event.url,
id: event.id,
timestamp: Date.now()
};
} else if (event instanceof NavigationCancel) {
record = {
type: 'NavigationCancel',
url: event.url,
id: event.id,
timestamp: Date.now()
};
} else if (event instanceof NavigationError) {
record = {
type: 'NavigationError',
url: event.url,
id: event.id,
timestamp: Date.now()
};
}

if (record) {
const currentHistory = this._history.getValue();
const updatedHistory = [...currentHistory, record].slice(-10); // Keep last 10 events
this._history.next(updatedHistory);
}
});
}

public clearHistory(): void {
this._history.next([]);
}
}

Summary

Angular Router Events provide a powerful way to track and respond to navigation changes in your application. Key points to remember:

  • Router events follow a predictable sequence from NavigationStart to NavigationEnd or error/cancel events
  • You can use these events for loading indicators, analytics tracking, and confirming navigation
  • Proper subscription management is crucial to prevent memory leaks
  • Leverage RxJS operators for efficient event handling
  • Consider creating centralized services for cross-component navigation concerns

By mastering Router Events, you can create more responsive, user-friendly applications that provide feedback during navigation and handle edge cases gracefully.

Additional Resources

Exercises

  1. Create a debug service that logs all router events to the console with timestamps.
  2. Implement a "back to top" feature that scrolls to the top of the page on every navigation.
  3. Build a breadcrumb component that updates based on the current route.
  4. Create a service that tracks how long users spend on each page using router events.
  5. Implement a "recently visited pages" feature that uses router events to build a history of pages the user has visited.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)