diff --git a/packages/core/src/render3/util/global_utils.ts b/packages/core/src/render3/util/global_utils.ts index e31770775bf..d3487b8a432 100644 --- a/packages/core/src/render3/util/global_utils.ts +++ b/packages/core/src/render3/util/global_utils.ts @@ -59,8 +59,10 @@ export const GLOBAL_PUBLISH_EXPANDO_KEY = 'ng'; // Typing for externally published global util functions // Ideally we should be able to use `NgGlobalPublishUtils` using declaration merging but that doesn't work with API extractor yet. // Have included the typings to have type safety when working with editors that support it (VSCode). -interface NgGlobalPublishUtils { +export interface ExternalGlobalUtils { ɵgetLoadedRoutes(route: any): any; + ɵnavigateByUrl(router: any, url: string): any; + ɵgetRouterInstance(injector: any): any; } const globalUtilsFunctions = { @@ -93,7 +95,6 @@ const globalUtilsFunctions = { 'enableProfiling': enableProfiling, }; type CoreGlobalUtilsFunctions = keyof typeof globalUtilsFunctions; -type ExternalGlobalUtilsFunctions = keyof NgGlobalPublishUtils; let _published = false; /** @@ -148,15 +149,15 @@ export type FrameworkAgnosticGlobalUtils = Omit< 'getDirectiveMetadata' > & { getDirectiveMetadata(directiveOrComponentInstance: any): DirectiveDebugMetadata | null; -}; +} & ExternalGlobalUtils; /** * Publishes the given function to `window.ng` from package other than @angular/core * So that it can be used from the browser console when an application is not in production. */ -export function publishExternalGlobalUtil( +export function publishExternalGlobalUtil( name: K, - fn: NgGlobalPublishUtils[K], + fn: ExternalGlobalUtils[K], ): void { publishUtil(name, fn); } diff --git a/packages/router/src/provide_router.ts b/packages/router/src/provide_router.ts index b29bcb6fc8b..67b28fe95ef 100644 --- a/packages/router/src/provide_router.ts +++ b/packages/router/src/provide_router.ts @@ -29,12 +29,13 @@ import { Type, ɵperformanceMarkFeature as performanceMarkFeature, ɵIS_ENABLED_BLOCKING_INITIAL_NAVIGATION as IS_ENABLED_BLOCKING_INITIAL_NAVIGATION, + ɵpublishExternalGlobalUtil, } from '@angular/core'; import {of, Subject} from 'rxjs'; import {INPUT_BINDER, RoutedComponentInputBinder} from './directives/router_outlet'; import {Event, NavigationError, stringifyEvent} from './events'; -import {RedirectCommand, Routes} from './models'; +import {RedirectCommand, Route, Routes} from './models'; import {NAVIGATION_ERROR_HANDLER, NavigationTransitions} from './navigation_transition'; import {Router} from './router'; import {InMemoryScrollingOptions, ROUTER_CONFIGURATION, RouterConfigOptions} from './router_config'; @@ -50,6 +51,7 @@ import { VIEW_TRANSITION_OPTIONS, ViewTransitionsFeatureOptions, } from './utils/view_transition'; +import {getLoadedRoutes, getRouterInstance, navigateByUrl} from './router_devtools'; /** * Sets up providers necessary to enable `Router` functionality for the application. @@ -88,6 +90,13 @@ import { * @returns A set of providers to setup a Router. */ export function provideRouter(routes: Routes, ...features: RouterFeatures[]): EnvironmentProviders { + if (typeof ngDevMode === 'undefined' || ngDevMode) { + // Publish this util when the router is provided so that the devtools can use it. + ɵpublishExternalGlobalUtil('ɵgetLoadedRoutes', getLoadedRoutes); + ɵpublishExternalGlobalUtil('ɵgetRouterInstance', getRouterInstance); + ɵpublishExternalGlobalUtil('ɵnavigateByUrl', navigateByUrl); + } + return makeEnvironmentProviders([ {provide: ROUTES, multi: true, useValue: routes}, typeof ngDevMode === 'undefined' || ngDevMode diff --git a/packages/router/src/router_devtools.ts b/packages/router/src/router_devtools.ts index b2aba86d23e..b6f32ca30aa 100644 --- a/packages/router/src/router_devtools.ts +++ b/packages/router/src/router_devtools.ts @@ -6,11 +6,31 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ɵpublishExternalGlobalUtil} from '@angular/core'; +import {Injector} from '@angular/core'; +import {Router} from './router'; import {Route} from './models'; +/** + * Returns the loaded routes for a given route. + */ export function getLoadedRoutes(route: Route): Route[] | undefined { return route._loadedRoutes; } -ɵpublishExternalGlobalUtil('ɵgetLoadedRoutes', getLoadedRoutes); +/** + * Returns the Router instance from the given injector, or null if not available. + */ +export function getRouterInstance(injector: Injector): Router | null { + return injector.get(Router, null, {optional: true}); +} + +/** + * Navigates the given router to the specified URL. + * Throws if the provided router is not an Angular Router. + */ +export function navigateByUrl(router: Router, url: string): Promise { + if (!(router instanceof Router)) { + throw new Error('The provided router is not an Angular Router.'); + } + return router.navigateByUrl(url); +} diff --git a/packages/router/test/router_devtools.spec.ts b/packages/router/test/router_devtools.spec.ts index b23ce25b038..7e0e56f72a6 100644 --- a/packages/router/test/router_devtools.spec.ts +++ b/packages/router/test/router_devtools.spec.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {Component, Injector} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {Router, RouterModule} from '../index'; -import {getLoadedRoutes} from '../src/router_devtools'; +import {getLoadedRoutes, getRouterInstance, navigateByUrl} from '../src/router_devtools'; @Component({template: '
simple standalone
'}) export class SimpleStandaloneComponent {} @@ -71,4 +71,38 @@ describe('router_devtools', () => { const loadedPath = loadedRoutes && loadedRoutes[0].path; expect(loadedPath).toEqual(undefined); }); + + describe('getRouterInstance', () => { + it('should return the Router instance from the injector', () => { + TestBed.configureTestingModule({ + imports: [RouterModule.forRoot([])], + }); + const injector = TestBed.inject(Injector); + const router = TestBed.inject(Router); + expect(getRouterInstance(injector)).toBe(router); + }); + + it('should return null if Router is not provided', () => { + const injector = Injector.create({providers: []}); + expect(getRouterInstance(injector)).toBeNull(); + }); + }); + + describe('navigateByUrl', () => { + it('should navigate to the given url', async () => { + TestBed.configureTestingModule({ + imports: [RouterModule.forRoot([{path: 'foo', component: SimpleStandaloneComponent}])], + }); + const router = TestBed.inject(Router); + const result = await navigateByUrl(router, '/foo'); + expect(result).toBeTrue(); + expect(router.url).toBe('/foo'); + }); + + it('should throw if not given a Router instance', async () => { + expect(() => navigateByUrl({} as unknown as Router, '/foo')).toThrowError( + 'The provided router is not an Angular Router.', + ); + }); + }); });