| title | Router |
|---|---|
| description | The live route, every navigation as a story, the live route config, router setup and a route lint. |
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.
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
routerOutletDataeach outlet passes (what the routed component reads withinject(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.
Every navigation as one story:
- Where it came from, and who started it: a
RouterLink, the code that callednavigate, 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,reloadortraverse) 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.
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.
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.
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.
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.
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
canMatchguards, lazy children that download,canDeactivateguards of the routes being left,canActivateChildandcanActivateguards, 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.
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) |
Route config mistakes, each with a fix:
- Unreachable routes after
**, and duplicate paths. - A
:paramthat shadows a later literal path. This includes a:paramroute with children orloadChildren, 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 (rulealways-run-parent), which runs them again on every navigation inside it, even one that only changes a query param or the fragment.- A
loadComponentroute whose component another route outside any lazy route uses ascomponent(rulelazy-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 aGETwhile it ran, and the sameGETagain 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.
routerLinkActivewithoutariaCurrentWhenActive. The check covers everyRouterLinkon 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.
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.
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.
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.
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. |
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.
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.
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.
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.
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.
| 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.
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.
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.
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.
Aborting and probing use the currentNavigation signal and Navigation.abort(), which older versions lack. On those versions the action returns an error.
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.
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.
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.
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.
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.