Skip to content

Latest commit

 

History

History
338 lines (246 loc) · 32 KB

router.md

File metadata and controls

338 lines (246 loc) · 32 KB
title Router
description The live route, every navigation as a story, the live route config, router setup and a route lint.
The live route, every navigation as one story, the live route config, the router setup and a route lint. Plus the routes your source declares.

Router

The Routes tab reads the running app's Router. The top section, Live router, has six views. The bottom section, Source route config, lists the routes your files declare. When more than one page is connected, a Page picker chooses which one you see. When the page runs more than one Angular app with a Router, an App picker chooses the app; see Several apps on one page.

What it shows

Current

The route the page is on:

  • The URL, and the browser URL when the two differ.
  • The navigation in flight, with an Abort button.
  • The document title, query params and fragment.
  • Active routes: each active route with its component, params, data, and guards and resolvers. Tags mark lazy routes, inherited params, and whether a data value is static, resolved or inherited. The title row says when the title is inherited.
  • Outlets: the outlet tree, with the inputs the router binds to each component and the routerOutletData each outlet passes (what the routed component reads with inject(ROUTER_OUTLET_DATA)). The data shows as a redacted preview of at most 300 characters. Each activated outlet has a Show in Components button that opens the Components tab on the page the Routes tab shows, with the routed component selected and scrolled into view. If the component is no longer on that page, the Components tab says so. The button is hidden when the Components inspector is off or the page reports no element for the routed component.

Navigations

Every navigation as one story:

  • Where it came from, and who started it: a RouterLink, the code that called navigate, or back and forward.
  • The extras, redirect chains and loops.
  • A phase bar: recognize, guards, resolve, activate.
  • Guards and resolvers, lazy loads, reused components, HTTP requests, scroll, and the title afterwards.
  • Router warnings, and the cancel or error reason. The tab explains NG04xxx errors.
  • What the withNavigationErrorHandler() handler did with a failed navigation: redirected (with the target), returned nothing or a value, or threw (with its message). When it redirects, the navigation names the error handler as the cause and keeps the error that triggered it, with its error code.
  • Navigation API: the browser Navigation API event (push, replace, reload or traverse) the navigation came with, and whether the user started it.
  • An open redirect risk badge when the target came from a query param. See Open redirect check.

Filter by URL, or check Only problems to keep the navigations that did not succeed and the ones in a loop. Only problems leaves out the navigations that Probe in app cancels, the way the problem count on the Navigations tab does. Each row has Replay, Compare (opens the Compare view with this navigation as the later one), Copy repro (a markdown repro) and HTTP calls. Replay is off for a navigation whose URL is redacted or not relative, and a note under the row says why. Export JSON saves the list.

HTTP calls lists the HttpClient calls the SSR & HTTP inspector recorded on the same page for that navigation, with method, URL, status and time, in two groups: While it ran (guards, resolvers, lazy loads) and After it ended (calls that started before the next navigation, at most 5 seconds after it ended, such as the ones the new page's components make). It needs the SSR & HTTP inspector and its HTTP setup. The HTTP row of each navigation counts the page's fetch and XHR requests from resource timing, which also works without that setup.

Clear empties the list and its loop warnings for the page you are looking at, so you can test a flow from a clean list without reloading the app. Other tabs keep their navigations. A navigation still running stays in the list. With actions.router set to false, Clear is off.

History changes outside the router

The page listens to the browser's Navigation API (window.navigation) and records its navigate events, without intercepting them. An event that comes while a router navigation runs, or right before one starts (back, forward), belongs to that navigation. The others are history changes the router did not make, such as history.pushState, location.hash or a hash link. The last five show in a History changes outside the router section above the list, with failed ones marked. They explain a browser URL that differs from the router's on Current. Browsers without the Navigation API record none.

Open redirect check

The page checks each navigation as it happens for a target taken from a query param, such as /login?returnUrl=%2Fadmin going to /admin. Whoever writes such a link chooses where the app sends the user, so the value needs an allow list:

Case What counts
A guard, resolver or the navigation error handler redirects The redirect target equals the value of any query param of the URL being navigated to. With guard recording on, it names the guard.
Your code calls navigate or navigateByUrl The new URL equals the value of a query param of the current URL whose name reads like a return URL, such as returnUrl, redirect or next. Clicked links don't count.

A value that is a full URL counts when its path matches. A target of / alone never counts. A flagged navigation gets the badge and an Open redirect row in Navigations, a warning in the hub's Messages dock, a line in explain-navigation, and a redirect-from-query warning in Lint.

Loop detection

The Navigations view looks for loops in the recorded navigations. A loop is a chain that comes back to a URL it already visited:

Loop What the view detects
redirect loop A chain of redirects returns to an earlier URL. The chain follows canMatch, guard, resolver and error handler redirects from one navigation to the next, plus the redirectTo inside each navigation.
navigation loop Your code calls navigate or navigateByUrl within 500 ms of the previous navigation ending, and the chain returns to an earlier URL.
redirect loop (NG04016) Angular stops a navigation with NG04016 because redirectTo entries of the config form a cycle.

To see one, open the Routes lab of the demo app (/examples/routes). Guard loop makes two guards redirect to each other five times, then to Summary (a redirect loop). Navigation ping-pong navigates between Details and Summary from code (a navigation loop).

When the view finds a loop, a Loop detected section appears above the list. It shows the cycle of URLs, such as /account → /login → /account. Under it, each hop names its cause: the guard, the redirectTo entry or the navigate call. A last line lists the navigation ids, how many times the chain came back, how it ended and the guards involved.

Each navigation in a loop gets a loop badge and a red edge. Its details gain a Loop row with the cycle and the hop this navigation caused.

Compare

Two recorded navigations side by side. Pick the Earlier and Later navigation. The view starts with the newest finished navigation and the one before it, and leaves out probes. Each part that differs gets a table with the field and its value in each navigation:

Part What it compares
Navigation URL, final URL, outcome, cancel code, trigger, redirect target and kind, error code
Query params The query params of the final URL
Route params The params of each activated route
Route data The data of each activated route, static and resolved
Outlets and components The route and component in each outlet, named outlets included
Guards The guards that ran, the overall verdict and each recorded guard result
Resolvers The resolvers that ran and each recorded resolver result
Phase timing The time of each phase
Lazy loads and requests Lazy loaded routes and the HTTP request count

The parts that are the same are listed under the tables. Params and data are kept for each navigation that ends, with secret-looking values redacted and each value cut at 120 characters. A navigation that was cancelled or failed activated no route, so the view says it has no route data to compare.

Config

The live route config. The tab merges lazy children in once they load, and marks the active branch.

  • Filter the routes by path, component or declaring file. While the filter is set, the summary shows the count, for example "2 of 14 routes", and a screen reader announces it as you type. If nothing matches, click Clear filters.
  • Test a URL and click Predict to see which route matches it, or the nearest ones.
  • Click What happens for a card that predicts going to the URL from the live config. It does not navigate or run app code. The card shows:
    • the redirects it follows, and the route chain and params
    • in order: the canMatch guards, lazy children that download, canDeactivate guards of the routes being left, canActivateChild and canActivate guards, resolvers, lazy components, the components that render and the title
    • the file each route is declared in, and the render mode from *.routes.server.ts
    • routes already active, marked, since they rerun guards only when their params change
    • why it stopped, when it reaches a function redirect, a redirect loop or a lazy route that has not loaded
  • Probe in app runs the real matcher without navigating.
  • Fill in the params of a route and click Go to navigate to it. With a param left empty, Go marks the empty field and names the params to fill in.
  • Read lazy reads the routes of a lazy route that has not loaded.

The result of Go and Read lazy shows under the row you clicked, and a screen reader announces it. Results and filled-in params belong to one page, so they are cleared when you pick another page.

Setup

How the router is set up (provideRouter, forRoot or other or unknown), the effective options with set or default badges, the enabled features, the strategies, the base href and hydration.

initialNavigation shows the mode that withEnabledBlockingInitialNavigation(), withDisabledInitialNavigation() or the forRoot option sets. The features map to these router features:

Feature Source
componentInputBinding withComponentInputBinding() or bindToComponentInputs
viewTransitions withViewTransitions() or enableViewTransitions
navigationErrorHandler withNavigationErrorHandler()
routerResources Router resources
injectorCleanup withExperimentalAutoCleanupInjectors()
preloading withPreloading() or preloadingStrategy, with the strategy name
scroller withInMemoryScrolling() or the forRoot scrolling options
debugTracing withDebugTracing() or enableTracing
platformNavigation withExperimentalPlatformNavigation() (Angular 21.1 and later)

Lint

Route config mistakes, each with a fix:

  • Unreachable routes after **, and duplicate paths.
  • A :param that shadows a later literal path. This includes a :param route with children or loadChildren, because when the URL ends at it the router matches it with no child and never reaches the literal route.
  • Empty-path redirects with pathMatch: 'prefix', which redirect every URL, and redirect cycles.
  • Redirect loops seen at runtime (rule redirect-loop), with the hops and the guards involved.
  • Deprecated class guards and canLoad.
  • Lazy chunks downloaded before a rejecting canActivate.
  • runGuardsAndResolvers: 'always' on a parent route with guards or resolvers (rule always-run-parent), which runs them again on every navigation inside it, even one that only changes a query param or the fragment.
  • A loadComponent route whose component another route outside any lazy route uses as component (rule lazy-component-eager). The component is in the main bundle already, so the lazy import saves nothing.
  • A resolver whose data the page fetches again (rule resolver-refetch): a navigation with resolvers made a GET while it ran, and the same GET again after it ended, before the next navigation. It needs the HTTP calls the SSR & HTTP inspector recorded on the page.
  • Missing or duplicate titles, and param or input typos.
  • routerLinkActive without ariaCurrentWhenActive. The check covers every RouterLink on the page, also one bound as [routerLink] on an element that isn't an anchor.
  • Emails in URLs, and return URLs taken from query params. A navigation the open redirect check flagged is a warning that names the param and the guard or code. Without one, the rule falls back to comparing each navigation with the query of the URL it left, as info.

Each finding says whether Angular throws, warns or does not warn. The lint skips lazy routes that have not loaded. It runs again after each navigation and config change, and keeps the current findings on screen while it does. Click Check again to rerun it.

If no check could run, the view says No checks ran and why: no page with a router is connected, or the page has not reported its route config yet. If the devtools server does not answer, the view shows an error with Retry.

Source route config

The routes declared in your files: *.routes.ts and *routing.module.ts files, the files they lazy load, and Analog pages. Each row shows the path, the component or target, guards and resolvers, the title and the declaring file. Once the live config is available, the tab collapses this table. Show table opens it.

Components rendered by the router show their route and outlet in the Components tab.

Where the data comes from

The overlay finds the Router through Angular's debug API and reports the route, navigations, config and setup. The server reads your route files for the source table, and *.routes.server.ts for render modes.

Finding the Router

The overlay reads the helper provideRouter() publishes (ng.ɵgetRouterInstance). For RouterModule.forRoot() apps, and on Angular 20.0 to 20.3.4 (which lack the helper), it looks for the Router token in the injectors instead. With several app roots that share one Router, the router that has routes or has navigated wins. Before Angular 16.2 neither lookup exists; see On older Angular.

Several apps on one page

A page can bootstrap more than one Angular app, with several bootstrapApplication() calls or micro-frontends. Each app root (an element with ng-version) that has its own Router counts as an app. Roots that share one Router count once.

When a page has more than one, the Live router heading shows an App picker. Each app is listed by its root component and tag, such as ShellApp <app-root>. The first app is the router that has routes or has navigated, else the first root. Pick another app to see its route, navigations, config, setup and lint. The buttons that act on the router (Go, Read lazy, Abort, Probe in app and Replay) run on the app you picked. A page with one app shows no picker.

Each app has an id: the tag of its root, with -2, -3 and so on for a second root with the same tag. The id stays the same while the root is on the page, and a root that replaces a removed one with the same tag gets the same id back. The router agent tools take that id, or the root component name, as app.

Some parts stay with the first app:

What Why
Outlets and links The overlay reads them from the whole page. They show on the first app only.
Preloads The overlay watches the first app's preloader.
Guard recording It wraps the first app's guards. On another app, Record each guard and resolver shows an error.
Follow navigations follow-navigations streams the first app's navigations.

Development builds

The live views need window.ng, so they need a development build. In a production build the overlay finds no Router, and Current says This page reports no Router.

When the debug API exists but lacks the provider helper (ng.ɵgetInjectorProviders), the tab runs in events-only mode. The route config is still read, so the tree and lint work. Setup detection is limited: the overlay can't find the preloader, the scroller or the router providers, so the Setup view says so and its features and options can show defaults.

Guard verdicts

The router reports one result for all the guards of a navigation. To see each guard's verdict and time, the devtools wrap every guard and resolver in the live config. Each row shows the guard, the route, its result (such as UrlTree /login) and its time.

Without that recording, the guards listed for a navigation are candidates: the canDeactivate guards of the page being left, and the canActivate and canActivateChild guards of the target.

How to use it

Find out why a navigation failed

Check Only problems to hide the navigations that succeeded outside a loop, and the probes. The phase bar shows where it stopped. The guard rows show which guard returned false or a UrlTree. Fix the code, then click Replay to run the same navigation again. Click Copy repro to paste a markdown repro into an issue.

Check which route a URL hits

Type the URL into Test a URL. Click Predict. A miss lists the nearest routes. Click What happens to list the redirects, guards, lazy chunks, resolvers and components of that navigation, in order. Click Probe in app to confirm with the real matcher. It runs canMatch and may load lazy chunks.

Clean up the config

Read the findings, most severe first. Each finding comes with a fix. Start with the ones where Angular stays silent. Click Check again after the app reloads.

Agent tools

Tool or resource What it does
pangular:explain-navigation Why a navigation failed or redirected, with any loop and the cause of each hop. Pass url or id to narrow it, limit for more than the last 5 (up to limits.navigations), perf for the slowest ones, or format: "repro" for a markdown repro of one navigation.
pangular:diff-navigations What changed between two navigations: params, data, guards, resolvers, outlets, components and timing. Pass a and b, or leave them out to compare the newest navigation with the last one to the same path.
pangular:follow-navigations New and changed navigations since a cursor, across pages. Pass the cursor back as since, with waitMs to wait for the next navigation.
pangular:preview-navigation What going to a URL would do, from the live config and without navigating: redirects, the route chain, guards, lazy chunks, resolvers and components in order, source files and render mode.
pangular:inspect-route The route the page is on. Pass component (a component class, tag or link text) for the route a component was rendered for, or a link state.
pangular:list-routes The live config with source files and example URLs. match predicts a URL, audit lists the guards of each page, resolveLazy loads the routes of a lazy route first.
pangular:lint-routes The lint findings, including redirect loops seen at runtime.
pangular:router-config The setup, including whether guard recording is on.
pangular:scan-render-mode Which render mode a URL gets, from *.routes.server.ts.
pangular:scan-routes Routes from your source files.
pangular:navigate Acts on the router: go, abort and replay.
pangular:probe-route Runs the matcher and canMatch guards for a URL without navigating.
pangular:record-guards Turns per-guard and per-resolver recording on or off.
pangular:router (resource) The active route tree and recent navigations of each page.

On a page that runs several apps, inspect-route, explain-navigation, diff-navigations, preview-navigation, list-routes, lint-routes and router-config take an optional app: the app's id or root component name. Without it they answer for the first app and name the others.

navigate and probe-route only accept same-origin relative URLs that start with /. With actions.router set to false, the server drops navigate and probe-route, and keeps record-guards and list-routes with resolveLazy. See Tools.

Limits and gotchas

On older Angular

Before Angular 16.2, Angular has neither ng.ɵgetRouterInstance nor ng.ɵgetInjectorProviders, so the overlay can't look the Router up by name. It reads the Router that a router link, the component an outlet renders or the root component holds, and asks the root injector for that class. If nothing on the page holds the Router, the tab shows no router data. See Supported Angular versions.

Guard recording is on by default

Record each guard and resolver in the Navigations view starts checked. Uncheck it to stop. Turning it off puts every original guard and resolver back. The page keeps the choice per browser tab, in sessionStorage, so it survives a reload. If the page does not answer, the checkbox returns to its previous state and the view shows the error. Agents use navigate with action: "instrument" and on.

Setup kind on Angular 20.0 to 20.3.4

Set up with tells provideRouter from forRoot by the ng.ɵgetRouterInstance helper. Angular 20.0 to 20.3.4 never publish it, so on those versions the overlay reads the root providers instead. The RouterModule.forRoot() guard token means forRoot or other, and the ROUTES token without that guard means provideRouter. If neither is there, the row shows unknown.

Abort and probe need Angular 20.2

Aborting and probing use the currentNavigation signal and Navigation.abort(), which older versions lack. On those versions the action returns an error.

Navigations before the devtools connected

The tab lists only the last one, marked before Pangular Inspector connected, without timing or guard details. It also lists a navigation still running at that moment.

Redaction

The devtools replace query, matrix and fragment values with secret-looking keys with [redacted]. They also redact tokens, Bearer values, and route params with secret-looking names such as :token, in the primary outlet and in named outlets. You can't replay a navigation with a redacted URL. See what the devtools redact.

History and config caps

The page keeps the last 50 navigations and 50 preloads. Set the navigation count with limits.navigations. Once older navigations are dropped, the Navigations view and explain-navigation say how many. The live config lists at most 200 routes per level (a route's children and its loaded lazy routes count as one level) and 1000 routes in total. The Config view and pangular:list-routes say how many routes were left out. The page reports at most 100 router links. After each DOM change, it checks at most 2000 elements that could hold a link. lint-routes and inspect-route say how many links past the first 100 were not checked. If the page has more elements than the check limit, the count is a minimum, and both tools also say how many elements were left out.

FAQ

Guard recording is off for that tab. Check Record each guard and resolver and run the navigation again. No. It runs the real matcher with skipLocationChange and stops after recognition. canActivate, canDeactivate and resolvers do not run. If a canMatch guard or the navigation error handler redirects, the probe stops the redirected navigation too and names its target. The probe runs your app's canMatch guards, so actions.router set to false turns it off, along with Go, Abort, Replay and Clear. Record each guard and resolver and Read lazy stay on. The live config is available, so it is the better source. Click Show table to open the source list.

Where to next

Routed components show their route and outlet. File routes, server calls and render modes for Analog apps. What is redacted, and how access is limited. Every tool a coding agent can call.