skip to content

In Angular, in what order does Router.events emit during a successful navigation, and which events can end a navigation?

level: middleimportance: should knowfreq 52%

answer

  1. start, recognise, guard, resolve, end
  2. RoutesRecognized after matching
  3. GuardsCheckStart and GuardsCheckEnd
  4. exactly one terminal event

basics

~10 s

A successful Angular navigation emits NavigationStart, RoutesRecognized, GuardsCheckStart, GuardsCheckEnd, ResolveStart, ResolveEnd, then NavigationEnd. Every navigation ends with exactly one of NavigationEnd, NavigationCancel, NavigationError or NavigationSkipped.

solid answer

~30 s

The core sequence is `NavigationStart` (with the requested URL), `RoutesRecognized` (the URL has been matched, redirects applied), `GuardsCheckStart` and `GuardsCheckEnd` around the `canDeactivate`/`canActivateChild`/`canActivate` phase, `ResolveStart` and `ResolveEnd` around resolvers, and finally `NavigationEnd`, emitted after the new components have been activated. `RouteConfigLoadStart`/`RouteConfigLoadEnd` appear when a lazy route config loads, and the per-route `ActivationStart`/`ChildActivationStart` and `ActivationEnd`/`ChildActivationEnd` events appear inside the guard and activation phases. Each navigation finishes with exactly one terminal event: `NavigationEnd` on success, `NavigationCancel` when a guard, redirect or newer navigation stops it, `NavigationError` on an unexpected error, or `NavigationSkipped` when it was ignored, for example a same-URL navigation.

code

ts · 26 lines
ts
import { Component, DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
  GuardsCheckEnd,
  NavigationCancel,
  NavigationEnd,
  NavigationError,
  NavigationSkipped,
  NavigationStart,
  Router,
  RouterOutlet,
} from '@angular/router';

@Component({ selector: 'app-root', imports: [RouterOutlet], template: '<router-outlet />' })
export class App {
  constructor() {
    inject(Router).events.pipe(takeUntilDestroyed(inject(DestroyRef))).subscribe((e) => {
      if (e instanceof NavigationStart) console.log(e.id, 'start', e.url, e.navigationTrigger);
      else if (e instanceof GuardsCheckEnd) console.log(e.id, 'guards passed?', e.shouldActivate);
      else if (e instanceof NavigationEnd) console.log(e.id, 'end', e.urlAfterRedirects);
      else if (e instanceof NavigationCancel) console.log(e.id, 'cancel code', e.code);
      else if (e instanceof NavigationError) console.log(e.id, 'error', e.error);
      else if (e instanceof NavigationSkipped) console.log(e.id, 'skipped code', e.code);
    });
  }
}

go deeper

for a junior

Recall the main milestones: start, routes recognised, guards, resolve, end, and that cancel and error are the other endings.

for a middle

Explain the full order, which events carry which data, and that exactly one of four terminal events ends each navigation.

for a senior

Use the sequence to diagnose stuck or bouncing navigations, recognising redirects as cancel-plus-new-start and superseded navigations by their codes.

for a principal

Decide which navigation telemetry the app should record from these events, and keep per-event listeners from spreading business logic across the app.

## Where the events come from `Router.events` is a stream of objects describing each step of every navigation. They are classes exported from `@angular/router`, so you tell them apart with `instanceof` (or their `type` property). Each navigation has a numeric `id`, carried on the events that extend `RouterEvent`, so events from overlapping navigations can be told apart. ## The successful path, step by step 1. **`NavigationStart`** - a navigation has begun. It carries `url`, `navigationTrigger` (`'imperative'` for `navigate()`/`navigateByUrl()`/`routerLink`, `'popstate'` for back/forward, `'hashchange'`) and `restoredState` for history navigations. 2. **`RouteConfigLoadStart` / `RouteConfigLoadEnd`** - only when a `loadChildren` route configuration has to be downloaded during matching. 3. **`RoutesRecognized`** - matching is finished and redirects are applied. It carries `urlAfterRedirects` and the target `state` snapshot. `canMatch` guards have already run by now, as part of matching. 4. **`GuardsCheckStart`** - the guard phase begins. While it runs, **`ChildActivationStart`** and **`ActivationStart`** fire for routes being activated. 5. **`GuardsCheckEnd`** - all guards finished; its `shouldActivate` property says whether they allowed the navigation. 6. **`ResolveStart`** / **`ResolveEnd`** - the resolver phase (and blocking route resources). It is emitted even when no resolvers are configured, and skipped only when no route needs its checks rerun, such as a query-only change under the default rerun policy. 7. **`ActivationEnd`** / **`ChildActivationEnd`** - fired per route as the router activates the new components. 8. **`NavigationEnd`** - the navigation completed. It carries `url` and `urlAfterRedirects`; by now the new components exist. A lazily loaded `loadComponent` chunk downloads between `ResolveEnd` and activation, which also emits the `RouteConfigLoad` pair. ## The four ways a navigation ends | Terminal event | Meaning | Typical cause | |---|---|---| | `NavigationEnd` | success | everything passed | | `NavigationCancel` | stopped on purpose, with a `code` | guard returned `false`, a redirect, a newer navigation, a resolver with no value, `abort()` | | `NavigationError` | an unexpected error | no route matched, a guard or resolver threw, a chunk failed to load | | `NavigationSkipped` | ignored, never really started | same-URL navigation with `onSameUrlNavigation: 'ignore'` | The router's documentation states the rule interviewers look for: **every navigation ends with exactly one of these**. Code that waits only for `NavigationEnd` will wait forever after a cancelled or failed navigation. ## Details that change the picture - A **redirect is two navigations**. A guard returning a `UrlTree` or `RedirectCommand` produces `NavigationCancel` (code `Redirect`) for the first navigation and a new `NavigationStart` for the target; the first one never emits `GuardsCheckEnd`. - A guard returning **`false`** emits `GuardsCheckEnd` with `shouldActivate: false`, then `NavigationCancel` with code `GuardRejected`. - A **same-URL** navigation that is ignored emits `NavigationSkipped` **without** a preceding `NavigationStart`. - If a new navigation starts while one is running, the older one ends with `NavigationCancel` (code `SupersededByNewNavigation`). - `Scroll` events are also in the stream, emitted after navigations when scroll restoration is configured; they are not tied to one phase. ## Using the sequence in practice Knowing the order tells you where each concern belongs: - **Page-view analytics** go on `NavigationEnd`, using `urlAfterRedirects`, so redirected and cancelled navigations are not counted as views. - **Document titles** are applied by the router's title strategy right after it emits `NavigationEnd`, so a listener that reads `document.title` synchronously inside `NavigationEnd` still sees the old title. - **Focus management** for accessibility (moving focus to the new page heading) belongs after `NavigationEnd`, once the new components exist. - **Knowing the destination early**: `RoutesRecognized` is the first event with the matched target state, useful for starting work before guards finish. One typing trap: not every event extends `RouterEvent`. `RouteConfigLoadStart`/`End`, the four activation events and `Scroll` are separate classes without `id` and `url`, so filtering with `instanceof RouterEvent` silently drops them. Filter by the concrete classes you need instead. ## Seeing it for real `provideRouter(routes, withDebugTracing())` logs every router event to the console, which is the quickest way to learn the order, or to find where a stuck navigation stops. It is a debugging aid and should not ship to production.

  • Why does a guard redirect produce two NavigationStart events?
    The router cannot turn the running navigation into a different one, so it ends the first navigation with `NavigationCancel` (code `Redirect`) and starts a new navigation to the redirect target, which has its own id and its own `NavigationStart`. The first navigation never reaches `GuardsCheckEnd`. Listeners that key on ids see two separate navigations.
  • When in this sequence are the new route components created?
    During activation, after `ResolveEnd` (and after any lazy `loadComponent` chunk has loaded) and before `NavigationEnd`. `ActivationEnd` and `ChildActivationEnd` fire as each route is activated, so by the time a listener sees `NavigationEnd`, the routed components already exist.

saying these in an interview costs you the question

  • Every navigation ends with NavigationEnd
  • Resolvers run before GuardsCheckStart
  • NavigationEnd fires before the new components are created
  • A redirect keeps the same navigation id and just changes its URL
  • NavigationSkipped is preceded by NavigationStart