Skip to content

Commit 43cb583

Browse files
JeanMecheAndrewKushnir
authored andcommitted
refactor(devtools): add debug router APIs (angular#64411)
This is a patch backport of angular#63081 PR Close angular#64411
1 parent 5f4f624 commit 43cb583

File tree

4 files changed

+74
-10
lines changed

4 files changed

+74
-10
lines changed

packages/core/src/render3/util/global_utils.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ export const GLOBAL_PUBLISH_EXPANDO_KEY = 'ng';
5959
// Typing for externally published global util functions
6060
// Ideally we should be able to use `NgGlobalPublishUtils` using declaration merging but that doesn't work with API extractor yet.
6161
// Have included the typings to have type safety when working with editors that support it (VSCode).
62-
interface NgGlobalPublishUtils {
62+
export interface ExternalGlobalUtils {
6363
ɵgetLoadedRoutes(route: any): any;
64+
ɵnavigateByUrl(router: any, url: string): any;
65+
ɵgetRouterInstance(injector: any): any;
6466
}
6567

6668
const globalUtilsFunctions = {
@@ -93,7 +95,6 @@ const globalUtilsFunctions = {
9395
'enableProfiling': enableProfiling,
9496
};
9597
type CoreGlobalUtilsFunctions = keyof typeof globalUtilsFunctions;
96-
type ExternalGlobalUtilsFunctions = keyof NgGlobalPublishUtils;
9798

9899
let _published = false;
99100
/**
@@ -148,15 +149,15 @@ export type FrameworkAgnosticGlobalUtils = Omit<
148149
'getDirectiveMetadata'
149150
> & {
150151
getDirectiveMetadata(directiveOrComponentInstance: any): DirectiveDebugMetadata | null;
151-
};
152+
} & ExternalGlobalUtils;
152153

153154
/**
154155
* Publishes the given function to `window.ng` from package other than @angular/core
155156
* So that it can be used from the browser console when an application is not in production.
156157
*/
157-
export function publishExternalGlobalUtil<K extends ExternalGlobalUtilsFunctions>(
158+
export function publishExternalGlobalUtil<K extends keyof ExternalGlobalUtils>(
158159
name: K,
159-
fn: NgGlobalPublishUtils[K],
160+
fn: ExternalGlobalUtils[K],
160161
): void {
161162
publishUtil(name, fn);
162163
}

packages/router/src/provide_router.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ import {
2929
Type,
3030
ɵperformanceMarkFeature as performanceMarkFeature,
3131
ɵIS_ENABLED_BLOCKING_INITIAL_NAVIGATION as IS_ENABLED_BLOCKING_INITIAL_NAVIGATION,
32+
ɵpublishExternalGlobalUtil,
3233
} from '@angular/core';
3334
import {of, Subject} from 'rxjs';
3435

3536
import {INPUT_BINDER, RoutedComponentInputBinder} from './directives/router_outlet';
3637
import {Event, NavigationError, stringifyEvent} from './events';
37-
import {RedirectCommand, Routes} from './models';
38+
import {RedirectCommand, Route, Routes} from './models';
3839
import {NAVIGATION_ERROR_HANDLER, NavigationTransitions} from './navigation_transition';
3940
import {Router} from './router';
4041
import {InMemoryScrollingOptions, ROUTER_CONFIGURATION, RouterConfigOptions} from './router_config';
@@ -50,6 +51,7 @@ import {
5051
VIEW_TRANSITION_OPTIONS,
5152
ViewTransitionsFeatureOptions,
5253
} from './utils/view_transition';
54+
import {getLoadedRoutes, getRouterInstance, navigateByUrl} from './router_devtools';
5355

5456
/**
5557
* Sets up providers necessary to enable `Router` functionality for the application.
@@ -88,6 +90,13 @@ import {
8890
* @returns A set of providers to setup a Router.
8991
*/
9092
export function provideRouter(routes: Routes, ...features: RouterFeatures[]): EnvironmentProviders {
93+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
94+
// Publish this util when the router is provided so that the devtools can use it.
95+
ɵpublishExternalGlobalUtil('ɵgetLoadedRoutes', getLoadedRoutes);
96+
ɵpublishExternalGlobalUtil('ɵgetRouterInstance', getRouterInstance);
97+
ɵpublishExternalGlobalUtil('ɵnavigateByUrl', navigateByUrl);
98+
}
99+
91100
return makeEnvironmentProviders([
92101
{provide: ROUTES, multi: true, useValue: routes},
93102
typeof ngDevMode === 'undefined' || ngDevMode

packages/router/src/router_devtools.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,31 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {ɵpublishExternalGlobalUtil} from '@angular/core';
9+
import {Injector} from '@angular/core';
10+
import {Router} from './router';
1011
import {Route} from './models';
1112

13+
/**
14+
* Returns the loaded routes for a given route.
15+
*/
1216
export function getLoadedRoutes(route: Route): Route[] | undefined {
1317
return route._loadedRoutes;
1418
}
1519

16-
ɵpublishExternalGlobalUtil('ɵgetLoadedRoutes', getLoadedRoutes);
20+
/**
21+
* Returns the Router instance from the given injector, or null if not available.
22+
*/
23+
export function getRouterInstance(injector: Injector): Router | null {
24+
return injector.get(Router, null, {optional: true});
25+
}
26+
27+
/**
28+
* Navigates the given router to the specified URL.
29+
* Throws if the provided router is not an Angular Router.
30+
*/
31+
export function navigateByUrl(router: Router, url: string): Promise<boolean> {
32+
if (!(router instanceof Router)) {
33+
throw new Error('The provided router is not an Angular Router.');
34+
}
35+
return router.navigateByUrl(url);
36+
}

packages/router/test/router_devtools.spec.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {Component} from '@angular/core';
9+
import {Component, Injector} from '@angular/core';
1010
import {TestBed} from '@angular/core/testing';
1111
import {Router, RouterModule} from '../index';
12-
import {getLoadedRoutes} from '../src/router_devtools';
12+
import {getLoadedRoutes, getRouterInstance, navigateByUrl} from '../src/router_devtools';
1313

1414
@Component({template: '<div>simple standalone</div>'})
1515
export class SimpleStandaloneComponent {}
@@ -71,4 +71,38 @@ describe('router_devtools', () => {
7171
const loadedPath = loadedRoutes && loadedRoutes[0].path;
7272
expect(loadedPath).toEqual(undefined);
7373
});
74+
75+
describe('getRouterInstance', () => {
76+
it('should return the Router instance from the injector', () => {
77+
TestBed.configureTestingModule({
78+
imports: [RouterModule.forRoot([])],
79+
});
80+
const injector = TestBed.inject(Injector);
81+
const router = TestBed.inject(Router);
82+
expect(getRouterInstance(injector)).toBe(router);
83+
});
84+
85+
it('should return null if Router is not provided', () => {
86+
const injector = Injector.create({providers: []});
87+
expect(getRouterInstance(injector)).toBeNull();
88+
});
89+
});
90+
91+
describe('navigateByUrl', () => {
92+
it('should navigate to the given url', async () => {
93+
TestBed.configureTestingModule({
94+
imports: [RouterModule.forRoot([{path: 'foo', component: SimpleStandaloneComponent}])],
95+
});
96+
const router = TestBed.inject(Router);
97+
const result = await navigateByUrl(router, '/foo');
98+
expect(result).toBeTrue();
99+
expect(router.url).toBe('/foo');
100+
});
101+
102+
it('should throw if not given a Router instance', async () => {
103+
expect(() => navigateByUrl({} as unknown as Router, '/foo')).toThrowError(
104+
'The provided router is not an Angular Router.',
105+
);
106+
});
107+
});
74108
});

0 commit comments

Comments
 (0)