Skip to content

Commit 7003e8d

Browse files
atscottthePunderWoman
authored andcommitted
feat(router): Publish Router's integration with platform Navigation API as experimental
This publishes the work that was done to integrate with the Navigation API as an experimental router feature. Browser support is limited and in active development. There are also known bugs in the browser implementations and only Chromium browsers supported deferred URL updates with the `precommitHandler`. Relates to #53321, which I would likely not mark as completed until this is at least in dev preview, which likely won't happen until it is widely available and potentially delayed until `precommitHandler` is widely available as well. The final form of this api might not even be a "router feature" in the end, but instead be something similar to what other frameworks have to provide different platform integrations (e.g. `provideNavigationRouter`). That would support omitting the history-based integration from the bundle when only the navigation integration is used. Alternatively, the current `provideRouter` could require one of `withHistory` or `withPlatformNavigation`.
1 parent 0ad3adc commit 7003e8d

File tree

7 files changed

+57
-22
lines changed

7 files changed

+57
-22
lines changed

goldens/public-api/router/index.api.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ export interface RouterFeature<FeatureKind extends RouterFeatureKind> {
801801
}
802802

803803
// @public
804-
export type RouterFeatures = PreloadingFeature | DebugTracingFeature | InitialNavigationFeature | InMemoryScrollingFeature | RouterConfigurationFeature | NavigationErrorHandlerFeature | ComponentInputBindingFeature | ViewTransitionsFeature | ExperimentalAutoCleanupInjectorsFeature | RouterHashLocationFeature;
804+
export type RouterFeatures = PreloadingFeature | DebugTracingFeature | InitialNavigationFeature | InMemoryScrollingFeature | RouterConfigurationFeature | NavigationErrorHandlerFeature | ComponentInputBindingFeature | ViewTransitionsFeature | ExperimentalAutoCleanupInjectorsFeature | RouterHashLocationFeature | ExperimentalPlatformNavigationFeature;
805805

806806
// @public
807807
export type RouterHashLocationFeature = RouterFeature<RouterFeatureKind.RouterHashLocationFeature>;
@@ -1144,6 +1144,9 @@ export function withEnabledBlockingInitialNavigation(): EnabledBlockingInitialNa
11441144
// @public
11451145
export function withExperimentalAutoCleanupInjectors(): ExperimentalAutoCleanupInjectorsFeature;
11461146

1147+
// @public
1148+
export function withExperimentalPlatformNavigation(): ExperimentalPlatformNavigationFeature;
1149+
11471150
// @public
11481151
export function withHashLocation(): RouterHashLocationFeature;
11491152

packages/router/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export {
8282
NavigationErrorHandlerFeature,
8383
PreloadingFeature,
8484
provideRouter,
85+
withExperimentalPlatformNavigation,
8586
provideRoutes,
8687
RouterConfigurationFeature,
8788
RouterFeature,

packages/router/src/private_export.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ export {RestoredState as ɵRestoredState} from './navigation_transition';
1111
export {loadChildren as ɵloadChildren} from './router_config_loader';
1212
export {ROUTER_PROVIDERS as ɵROUTER_PROVIDERS} from './router_module';
1313
export {afterNextNavigation as ɵafterNextNavigation} from './utils/navigations';
14-
export {withPlatformNavigation as ɵwithPlatformNavigation} from './provide_router';

packages/router/src/provide_router.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,36 @@ export function withInMemoryScrolling(
237237
}
238238

239239
/**
240-
* Enables the use of the browser's `History` API for navigation.
240+
* A type alias for providers returned by `withExperimentalPlatformNavigation` for use with `provideRouter`.
241+
*
242+
* @see {@link withExperimentalPlatformNavigation}
243+
* @see {@link provideRouter}
244+
*
245+
* @experimental 21.1
246+
*/
247+
export type ExperimentalPlatformNavigationFeature =
248+
RouterFeature<RouterFeatureKind.ExperimentalPlatformNavigationFeature>;
249+
250+
/**
251+
* Enables interop with the browser's `Navigation` API for router navigations.
241252
*
242253
* @description
243-
* This function provides a `Location` strategy that uses the browser's `History` API.
244-
* It is required when using features that rely on `history.state`. For example, the
245-
* `state` object in `NavigationExtras` is passed to `history.pushState` or
246-
* `history.replaceState`.
254+
*
255+
* CRITICAL: This feature is _highly_ experimental and should not be used in production. Browser support
256+
* is limited and in active development. Use only for experimentation and feedback purposes.
257+
*
258+
* This function provides a `Location` strategy that uses the browser's `Navigation` API.
259+
* By using the platform's Navigation APIs, the Router is able to provide native
260+
* browser navigation capabilities. Some advantages include:
261+
*
262+
* - The ability to intercept navigations triggered outside the Router. This allows plain anchor
263+
* elements _without_ `RouterLink` directives to be intercepted by the Router and converted to SPA navigations.
264+
* - Native scroll and focus restoration support by the browser, without the need for custom implementations.
265+
* - Communication of ongoing navigations to the browser, enabling built-in features like
266+
* accessibility announcements, loading indicators, stop buttons, and performance measurement APIs.
267+
268+
* NOTE: Deferred entry updates are not part of the interop 2025 Navigation API commitments so the "ongoing navigation"
269+
* communication support is limited.
247270
*
248271
* @usageNotes
249272
*
@@ -254,14 +277,19 @@ export function withInMemoryScrolling(
254277
*
255278
* bootstrapApplication(AppComponent, {
256279
* providers: [
257-
* provideRouter(appRoutes, withPlatformNavigation())
280+
* provideRouter(appRoutes, withExperimentalPlatformNavigation())
258281
* ]
259282
* });
260283
* ```
284+
*
285+
* @see https://github.com/WICG/navigation-api?tab=readme-ov-file#problem-statement
286+
* @see https://developer.chrome.com/docs/web-platform/navigation-api/
287+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API
261288
*
289+
* @experimental 21.1
262290
* @returns A `RouterFeature` that enables the platform navigation.
263291
*/
264-
export function withPlatformNavigation() {
292+
export function withExperimentalPlatformNavigation(): ExperimentalPlatformNavigationFeature {
265293
const devModeLocationCheck =
266294
typeof ngDevMode === 'undefined' || ngDevMode
267295
? [
@@ -270,10 +298,10 @@ export function withPlatformNavigation() {
270298
if (!(locationInstance instanceof ɵNavigationAdapterForLocation)) {
271299
const locationConstructorName = (locationInstance as any).constructor.name;
272300
let message =
273-
`'withPlatformNavigation' provides a 'Location' implementation that ensures navigation APIs are consistently used.` +
301+
`'withExperimentalPlatformNavigation' provides a 'Location' implementation that ensures navigation APIs are consistently used.` +
274302
` An instance of ${locationConstructorName} was found instead.`;
275303
if (locationConstructorName === 'SpyLocation') {
276-
message += ` One of 'RouterTestingModule' or 'provideLocationMocks' was likely used. 'withPlatformNavigation' does not work with these because they override the Location implementation.`;
304+
message += ` One of 'RouterTestingModule' or 'provideLocationMocks' was likely used. 'withExperimentalPlatformNavigation' does not work with these because they override the Location implementation.`;
277305
}
278306
throw new Error(message);
279307
}
@@ -285,7 +313,7 @@ export function withPlatformNavigation() {
285313
{provide: Location, useClass: ɵNavigationAdapterForLocation},
286314
devModeLocationCheck,
287315
];
288-
return routerFeature(RouterFeatureKind.InMemoryScrollingFeature, providers);
316+
return routerFeature(RouterFeatureKind.ExperimentalPlatformNavigationFeature, providers);
289317
}
290318

291319
export function getBootstrapListener() {
@@ -910,7 +938,8 @@ export type RouterFeatures =
910938
| ComponentInputBindingFeature
911939
| ViewTransitionsFeature
912940
| ExperimentalAutoCleanupInjectorsFeature
913-
| RouterHashLocationFeature;
941+
| RouterHashLocationFeature
942+
| ExperimentalPlatformNavigationFeature;
914943

915944
/**
916945
* The list of features as an enum to uniquely type each feature.
@@ -927,4 +956,5 @@ export const enum RouterFeatureKind {
927956
ComponentInputBindingFeature,
928957
ViewTransitionsFeature,
929958
ExperimentalAutoCleanupInjectorsFeature,
959+
ExperimentalPlatformNavigationFeature,
930960
}

packages/router/test/computed_state_restoration.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {expect} from '@angular/private/testing/matchers';
1313
import {Router, RouterModule, RouterOutlet, UrlTree, withRouterConfig} from '../index';
1414
import {EMPTY, of} from 'rxjs';
1515

16-
import {provideRouter, withPlatformNavigation} from '../src/provide_router';
16+
import {provideRouter, withExperimentalPlatformNavigation} from '../src/provide_router';
1717
import {isUrlTree} from '../src/url_tree';
1818
import {timeout, useAutoTick} from './helpers';
1919
import {afterNextNavigation} from '../src/utils/navigations';
@@ -127,7 +127,7 @@ for (const browserAPI of ['navigation', 'history'] as const) {
127127
resolveNavigationPromiseOnError: true,
128128
}),
129129
browserAPI === 'navigation'
130-
? withPlatformNavigation()
130+
? withExperimentalPlatformNavigation()
131131
: (makeEnvironmentProviders([]) as any),
132132
),
133133
],

packages/router/test/integration/integration.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
RoutesRecognized,
3636
} from '../../index';
3737

38-
import {provideRouter, withPlatformNavigation} from '../../src/provide_router';
38+
import {provideRouter, withExperimentalPlatformNavigation} from '../../src/provide_router';
3939
import {
4040
BlankCmp,
4141
CollectParamsCmp,
@@ -87,7 +87,7 @@ for (const browserAPI of ['navigation', 'history'] as const) {
8787
provideRouter(
8888
[{path: 'simple', component: SimpleCmp}],
8989
browserAPI === 'navigation'
90-
? withPlatformNavigation()
90+
? withExperimentalPlatformNavigation()
9191
: (makeEnvironmentProviders([]) as any),
9292
),
9393
],

packages/router/test/with_platform_navigation.spec.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {TestBed} from '@angular/core/testing';
1010
import {provideRouter, Router} from '../src';
11-
import {withPlatformNavigation, withRouterConfig} from '../src/provide_router';
11+
import {withExperimentalPlatformNavigation, withRouterConfig} from '../src/provide_router';
1212
import {withBody} from '@angular/private/testing';
1313
import {
1414
PlatformLocation,
@@ -27,7 +27,9 @@ import {timeout, useAutoTick} from './helpers';
2727

2828
describe('withPlatformNavigation feature', () => {
2929
beforeEach(() => {
30-
TestBed.configureTestingModule({providers: [provideRouter([], withPlatformNavigation())]});
30+
TestBed.configureTestingModule({
31+
providers: [provideRouter([], withExperimentalPlatformNavigation())],
32+
});
3133
});
3234

3335
it('provides FakeNavigation by default', () => {
@@ -170,7 +172,7 @@ describe('withPlatformNavigation feature', () => {
170172
providers: [
171173
provideRouter(
172174
[{path: '**', children: []}],
173-
withPlatformNavigation(),
175+
withExperimentalPlatformNavigation(),
174176
withRouterConfig({urlUpdateStrategy: 'eager'}),
175177
),
176178
],
@@ -203,7 +205,7 @@ describe('withPlatformNavigation feature', () => {
203205
describe('configuration error', () => {
204206
it('throws an error mentioning SpyLocation and the location mocks', () => {
205207
TestBed.configureTestingModule({
206-
providers: [provideRouter([], withPlatformNavigation()), provideLocationMocks()],
208+
providers: [provideRouter([], withExperimentalPlatformNavigation()), provideLocationMocks()],
207209
});
208210
expect(() => TestBed.inject(Location)).toThrowError(/SpyLocation.*provideLocationMocks/);
209211
});
@@ -215,7 +217,7 @@ if (typeof window !== 'undefined' && 'navigation' in window) {
215217
beforeEach(() => {
216218
TestBed.configureTestingModule({
217219
providers: [
218-
provideRouter([{path: '**', children: []}], withPlatformNavigation()),
220+
provideRouter([{path: '**', children: []}], withExperimentalPlatformNavigation()),
219221
{provide: PlatformLocation, useClass: BrowserPlatformLocation},
220222
{provide: PlatformNavigation, useFactory: () => navigation},
221223
],

0 commit comments

Comments
 (0)