Skip to content

Commit cfb26f5

Browse files
AleksanderBodurriAndrewKushnir
authored andcommitted
feat(devtools): use router state for active route detection
Replace URL-based active route detection with direct traversal of the ActivatedRoute tree. This solution is more reliable than the previous approach because it directly compares router tree configuration objects against the active router instance state with router.routerState.
1 parent 55be477 commit cfb26f5

16 files changed

Lines changed: 222 additions & 26 deletions

devtools/projects/ng-devtools-backend/src/lib/router-tree.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ describe('parseRoutes', () => {
126126
'data': [],
127127
'isAux': true,
128128
'isLazy': false,
129-
'isActive': undefined,
129+
'isActive': false,
130130
},
131131
{
132132
'component': 'component-two',
@@ -141,7 +141,7 @@ describe('parseRoutes', () => {
141141
'data': [{'key': 'name', 'value': 'component-two'}],
142142
'isAux': false,
143143
'isLazy': false,
144-
'isActive': undefined,
144+
'isActive': false,
145145
'children': [
146146
{
147147
'component': 'component-two-one',
@@ -156,7 +156,7 @@ describe('parseRoutes', () => {
156156
'data': [],
157157
'isAux': false,
158158
'isLazy': false,
159-
'isActive': undefined,
159+
'isActive': false,
160160
},
161161
{
162162
'component': 'component-two-two',
@@ -171,7 +171,7 @@ describe('parseRoutes', () => {
171171
'data': [],
172172
'isAux': false,
173173
'isLazy': false,
174-
'isActive': undefined,
174+
'isActive': false,
175175
},
176176
],
177177
},
@@ -187,7 +187,7 @@ describe('parseRoutes', () => {
187187
'data': [],
188188
'isAux': false,
189189
'isLazy': true,
190-
'isActive': undefined,
190+
'isActive': false,
191191
},
192192
{
193193
'component': 'no-name-route',
@@ -201,7 +201,7 @@ describe('parseRoutes', () => {
201201
'data': [],
202202
'isAux': false,
203203
'isLazy': false,
204-
'isActive': undefined,
204+
'isActive': false,
205205
'redirectTo': 'redirectTo',
206206
},
207207
{
@@ -216,7 +216,7 @@ describe('parseRoutes', () => {
216216
'data': [],
217217
'isAux': false,
218218
'isLazy': false,
219-
'isActive': undefined,
219+
'isActive': false,
220220
'redirectTo': '[Function]',
221221
},
222222
{
@@ -231,7 +231,7 @@ describe('parseRoutes', () => {
231231
'data': [],
232232
'isAux': false,
233233
'isLazy': false,
234-
'isActive': undefined,
234+
'isActive': false,
235235
'redirectTo': 'redirectResolver()',
236236
},
237237
],

devtools/projects/ng-devtools-backend/src/lib/router-tree.ts

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {Route} from '../../../protocol';
10-
import type {Route as AngularRoute} from '@angular/router';
10+
import type {Route as AngularRoute, ActivatedRoute} from '@angular/router';
1111

1212
export type RoutePropertyType = RouteGuard | 'providers' | 'component' | 'redirectTo' | 'title';
1313

@@ -18,15 +18,64 @@ const routeGuards = ['canActivate', 'canActivateChild', 'canDeactivate', 'canMat
1818
type Routes = any;
1919
type Router = any;
2020

21+
/**
22+
* Recursively traverses the ActivatedRoute tree and collects all routeConfig objects.
23+
* @param activatedRoute - The ActivatedRoute to start traversal from
24+
* @param activeRoutes - Set to collect active Route configuration objects
25+
* @returns Set of active Route configuration objects
26+
*/
27+
function collectActiveRouteConfigs(
28+
activatedRoute: ActivatedRoute,
29+
activeRoutes: Set<AngularRoute> = new Set(),
30+
): Set<AngularRoute> {
31+
// Get the routeConfig for this ActivatedRoute
32+
const routeConfig = activatedRoute.routeConfig;
33+
if (routeConfig) {
34+
activeRoutes.add(routeConfig);
35+
}
36+
37+
// Recursively process all children
38+
const children = activatedRoute.children || [];
39+
for (const child of children) {
40+
collectActiveRouteConfigs(child, activeRoutes);
41+
}
42+
43+
return activeRoutes;
44+
}
45+
46+
/**
47+
* Gets the set of currently active Route configuration objects from the router state.
48+
* This function synchronously reads the current router state without waiting for navigation events.
49+
*
50+
* @param router - The Angular Router instance
51+
* @returns A Set containing all Route configuration objects that are currently active
52+
*
53+
* @example
54+
* ```ts
55+
* const activeRoutes = getActiveRouteConfigs(router);
56+
* // activeRoutes is a Set<Route> containing all currently active route configurations
57+
* ```
58+
*/
59+
export function getActiveRouteConfigs(router: Router): Set<AngularRoute> {
60+
const rootActivatedRoute = router.routerState?.root;
61+
if (!rootActivatedRoute) {
62+
return new Set();
63+
}
64+
65+
return collectActiveRouteConfigs(rootActivatedRoute);
66+
}
67+
2168
export function parseRoutes(router: Router): Route {
22-
const currentUrl = router.stateManager?.routerState?.snapshot?.url;
2369
const rootName = 'App Root';
2470
const rootChildren = router.config;
2571

72+
// Get the set of active Route configuration objects from the router state
73+
const activeRouteConfigs = getActiveRouteConfigs(router);
74+
2675
const root: Route = {
2776
component: rootName,
2877
path: rootName,
29-
children: rootChildren ? assignChildrenToParent(null, rootChildren, currentUrl) : [],
78+
children: rootChildren ? assignChildrenToParent(null, rootChildren, activeRouteConfigs) : [],
3079
isAux: false,
3180
isLazy: false,
3281
isActive: true, // Root is always active.
@@ -52,7 +101,7 @@ function getProviderName(child: any): string[] {
52101
function assignChildrenToParent(
53102
parentPath: string | null,
54103
children: Routes,
55-
currentUrl: string,
104+
activeRouteConfigs: Set<AngularRoute>,
56105
): Route[] {
57106
return children.map((child: AngularRoute) => {
58107
const childName = childRouteName(child);
@@ -66,8 +115,9 @@ function assignChildrenToParent(
66115
const isAux = Boolean(child.outlet);
67116
const isLazy = Boolean(child.loadChildren || child.loadComponent);
68117

69-
const pathWithoutParams = routePath.split('/:')[0];
70-
const isActive = currentUrl?.startsWith(pathWithoutParams);
118+
// Check if this route configuration object is in the active routes set
119+
// This is the direct reference to the Route object from router.config
120+
const isActive = activeRouteConfigs.has(child);
71121

72122
const routeConfig: Route = {
73123
pathMatch: child.pathMatch,
@@ -93,7 +143,11 @@ function assignChildrenToParent(
93143
}
94144

95145
if (childDescendents) {
96-
routeConfig.children = assignChildrenToParent(routeConfig.path, childDescendents, currentUrl);
146+
routeConfig.children = assignChildrenToParent(
147+
routeConfig.path,
148+
childDescendents,
149+
activeRouteConfigs,
150+
);
97151
}
98152

99153
if (child.data) {

devtools/src/app/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ng_project(
2626
"//devtools/src:demo_application_environment",
2727
"//devtools/src:demo_application_operations",
2828
"//devtools/src/app/demo-app",
29+
"//devtools/src/app/demo-app/auxiliary",
2930
"//devtools/src/app/devtools-app",
3031
],
3132
)

devtools/src/app/demo-app/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ ng_project(
6060
"//devtools/projects/ng-devtools-backend",
6161
"//devtools/src:communication",
6262
"//devtools/src:zone-unaware-iframe_message_bus",
63+
"//devtools/src/app/demo-app/auxiliary",
6364
"//devtools/src/app/demo-app/todo",
6465
],
6566
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
load("//devtools/tools:defaults.bzl", "ng_project", "sass_binary")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
sass_binary(
6+
name = "auxiliary_component_styles",
7+
src = "auxiliary.component.scss",
8+
)
9+
10+
ng_project(
11+
name = "auxiliary",
12+
srcs = ["auxiliary.component.ts"],
13+
angular_assets = [
14+
"auxiliary.component.html",
15+
":auxiliary_component_styles",
16+
],
17+
deps = [
18+
"//:node_modules/@angular/core",
19+
],
20+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>Auxiliary route is working!</p>

devtools/src/app/demo-app/auxiliary/auxiliary.component.scss

Whitespace-only changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import {Component} from '@angular/core';
10+
11+
@Component({
12+
selector: 'app-auxiliary',
13+
templateUrl: './auxiliary.component.html',
14+
})
15+
export class AuxiliaryComponent {}

devtools/src/app/demo-app/demo-app.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
import {ZippyComponent} from './zippy.component';
2727
import {HeavyComponent} from './heavy.component';
2828
import {SamplePropertiesComponent} from './sample-properties.component';
29-
import {RouterOutlet} from '@angular/router';
29+
import {RouterOutlet, RouterModule} from '@angular/router';
3030
import {CookieRecipe} from './cookies.component';
3131

3232
// structual directive example
@@ -57,6 +57,7 @@ export class StructuralDirective {
5757
HeavyComponent,
5858
SamplePropertiesComponent,
5959
RouterOutlet,
60+
RouterModule,
6061
CookieRecipe,
6162
],
6263
})

devtools/src/app/demo-app/demo-app.routes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {DEVTOOLS_BACKEND_URI, DEVTOOLS_FRONTEND_URI} from '../../communication';
1616

1717
import {DemoAppComponent} from './demo-app.component';
1818
import {ZippyComponent} from './zippy.component';
19+
import {AuxiliaryComponent} from './auxiliary/auxiliary.component';
1920

2021
export const DEMO_ROUTES: Routes = [
2122
{
@@ -26,6 +27,11 @@ export const DEMO_ROUTES: Routes = [
2627
path: '',
2728
loadChildren: () => import('./todo/app.module').then((m) => m.AppModule),
2829
},
30+
{
31+
path: 'aux',
32+
component: AuxiliaryComponent,
33+
outlet: 'aux',
34+
},
2935
],
3036
providers: [
3137
provideEnvironmentInitializer(() => {

0 commit comments

Comments
 (0)