Angular

How do Router events trigger change detection in Angular v21 zoneless apps?

March 18, 2026

download ready
Thank You
Your submission has been received.
We will be in touch and contact you soon!

In Angular v21 zoneless mode, Router events don't automatically trigger change detection since Zone.js patching is removed. Navigation completes but UI stays stale until signals update or explicit CD runs. Use router signals or effects to fix this.

Solutions:

  1. Preferred: routerState.signal() for reactive route data
  2. Alternative: effect() on router events
  3. Fallback: ChangeDetectorRef.markDirty()

Code Example:

Code

import { Component, effect, inject, signal } from '@angular/core';
import { Router, RouterState, NavigationEnd } from '@angular/router';

@Component({
  template: `
    <p>Current: {{ currentPath() }}</p>
    <a routerLink="/dashboard">Dashboard</a>
    <router-outlet />
  `
})
export class AppComponent {
  private router = inject(Router);
  private routerState = inject(RouterState);
  
  //  Solution 1: Reactive router signal (Recommended)
  currentPath = this.routerState.signal('url');
  
  // Solution 2: Effect on router events (Side effects)
  constructor() {
    effect(() => {
      this.router.events.pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))
        .subscribe(() => {
          // Force signal read to trigger CD
          this.currentPath();
        });
    });
  }
  
  // Solution 3: Manual CD trigger (Legacy)
  onNavigate() {
    this.cdr.markDirty(); // Only if no signals used
  }
}
      
Hire Now!

Need Help with Angular Development ?

Work with our skilled angular developers to accelerate your project and boost its performance.
**Hire now**Hire Now**Hire Now**Hire now**Hire now

How do Router events trigger change detection in Angular v21 zoneless apps?

In Angular v21 zoneless mode, Router events don't automatically trigger change detection since Zone.js patching is removed. Navigation completes but UI stays stale until signals update or explicit CD runs. Use router signals or effects to fix this.

Solutions:

  1. Preferred: routerState.signal() for reactive route data
  2. Alternative: effect() on router events
  3. Fallback: ChangeDetectorRef.markDirty()

Code Example:

Code

import { Component, effect, inject, signal } from '@angular/core';
import { Router, RouterState, NavigationEnd } from '@angular/router';

@Component({
  template: `
    <p>Current: {{ currentPath() }}</p>
    <a routerLink="/dashboard">Dashboard</a>
    <router-outlet />
  `
})
export class AppComponent {
  private router = inject(Router);
  private routerState = inject(RouterState);
  
  //  Solution 1: Reactive router signal (Recommended)
  currentPath = this.routerState.signal('url');
  
  // Solution 2: Effect on router events (Side effects)
  constructor() {
    effect(() => {
      this.router.events.pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))
        .subscribe(() => {
          // Force signal read to trigger CD
          this.currentPath();
        });
    });
  }
  
  // Solution 3: Manual CD trigger (Legacy)
  onNavigate() {
    this.cdr.markDirty(); // Only if no signals used
  }
}