Skip to content

Commit 332461b

Browse files
atscottdylhunn
authored andcommitted
feat(router): Add ability to override onSameUrlNavigation default per-navigation (#48050)
The router providers a configurable `onSameUrlNavigation` value that allows developers to configure whether navigations to the same URL as the current one should be processed or ignored. However, this only acts as a default value and there isn't an API for easily overriding this for a single navigation. Instead, developers are forced to update the value of the property on the router instance and remember to reset it. This feature fills a small gap in the Router APIs that enables developers to accomplish the task of force reloading a bit easier. Lengthy discussion about this here: #21115 PR Close #48050
1 parent 6a8ab30 commit 332461b

File tree

7 files changed

+103
-30
lines changed

7 files changed

+103
-30
lines changed

goldens/public-api/router/index.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ export interface Navigation {
379379

380380
// @public
381381
export interface NavigationBehaviorOptions {
382+
onSameUrlNavigation?: Extract<OnSameUrlNavigation, 'reload'>;
382383
replaceUrl?: boolean;
383384
skipLocationChange?: boolean;
384385
state?: {
@@ -493,6 +494,9 @@ export class NoPreloading implements PreloadingStrategy {
493494
static ɵprov: i0.ɵɵInjectableDeclaration<NoPreloading>;
494495
}
495496

497+
// @public
498+
export type OnSameUrlNavigation = 'reload' | 'ignore';
499+
496500
// @public
497501
export class OutletContext {
498502
// (undocumented)
@@ -677,7 +681,7 @@ export class Router {
677681
// (undocumented)
678682
ngOnDestroy(): void;
679683
// @deprecated
680-
onSameUrlNavigation: 'reload' | 'ignore';
684+
onSameUrlNavigation: OnSameUrlNavigation;
681685
// @deprecated
682686
paramsInheritanceStrategy: 'emptyOnly' | 'always';
683687
parseUrl(url: string): UrlTree;
@@ -709,7 +713,7 @@ export const ROUTER_INITIALIZER: InjectionToken<(compRef: ComponentRef<any>) =>
709713
// @public
710714
export interface RouterConfigOptions {
711715
canceledNavigationResolution?: 'replace' | 'computed';
712-
onSameUrlNavigation?: 'reload' | 'ignore';
716+
onSameUrlNavigation?: OnSameUrlNavigation;
713717
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
714718
urlUpdateStrategy?: 'deferred' | 'eager';
715719
}

packages/router/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export {RouterLink, RouterLinkWithHref} from './directives/router_link';
1212
export {RouterLinkActive} from './directives/router_link_active';
1313
export {RouterOutlet, RouterOutletContract} from './directives/router_outlet';
1414
export {ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart, Event, EventType, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationCancellationCode as NavigationCancellationCode, NavigationEnd, NavigationError, NavigationSkipped, NavigationSkippedCode, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouterEvent, RoutesRecognized, Scroll} from './events';
15-
export {CanActivate, CanActivateChild, CanActivateChildFn, CanActivateFn, CanDeactivate, CanDeactivateFn, CanLoad, CanLoadFn, CanMatch, CanMatchFn, Data, DefaultExport, LoadChildren, LoadChildrenCallback, NavigationBehaviorOptions, QueryParamsHandling, Resolve, ResolveData, ResolveFn, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
15+
export {CanActivate, CanActivateChild, CanActivateChildFn, CanActivateFn, CanDeactivate, CanDeactivateFn, CanLoad, CanLoadFn, CanMatch, CanMatchFn, Data, DefaultExport, LoadChildren, LoadChildrenCallback, NavigationBehaviorOptions, OnSameUrlNavigation, QueryParamsHandling, Resolve, ResolveData, ResolveFn, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
1616
export {Navigation, NavigationExtras, UrlCreationOptions} from './navigation_transition';
1717
export {DefaultTitleStrategy, TitleStrategy} from './page_title_strategy';
1818
export {DebugTracingFeature, DisabledInitialNavigationFeature, EnabledBlockingInitialNavigationFeature, InitialNavigationFeature, InMemoryScrollingFeature, PreloadingFeature, provideRouter, provideRoutes, RouterConfigurationFeature, RouterFeature, RouterFeatures, withDebugTracing, withDisabledInitialNavigation, withEnabledBlockingInitialNavigation, withInMemoryScrolling, withPreloading, withRouterConfig} from './provide_router';

packages/router/src/models.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,31 @@ import {DeprecatedLoadChildren} from './deprecated_load_children';
1313
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
1414
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
1515

16+
/**
17+
* How to handle a navigation request to the current URL. One of:
18+
*
19+
* - `'ignore'` : The router ignores the request it is the same as the current state.
20+
* - `'reload'` : The router processes the URL even if it is not different from the current state.
21+
* One example of when you might want this option is if a `canMatch` guard depends on
22+
* application state and initially rejects navigation to a route. After fixing the state, you want
23+
* to re-navigate to the same URL so the route with the `canMatch` guard can activate.
24+
*
25+
* Note that this only configures whether the Route reprocesses the URL and triggers related
26+
* action and events like redirects, guards, and resolvers. By default, the router re-uses a
27+
* component instance when it re-navigates to the same component type without visiting a different
28+
* component first. This behavior is configured by the `RouteReuseStrategy`. In order to reload
29+
* routed components on same url navigation, you need to set `onSameUrlNavigation` to `'reload'`
30+
* _and_ provide a `RouteReuseStrategy` which returns `false` for `shouldReuseRoute`. Additionally,
31+
* resolvers and most guards for routes do not run unless the path or path params changed
32+
* (configured by `runGuardsAndResolvers`).
33+
*
34+
* @publicApi
35+
* @see RouteReuseStrategy
36+
* @see RunGuardsAndResolvers
37+
* @see NavigationBehaviorOptions
38+
* @see RouterConfigOptions
39+
*/
40+
export type OnSameUrlNavigation = 'reload'|'ignore';
1641

1742
/**
1843
* Represents a route configuration for the Router service.
@@ -550,11 +575,19 @@ export interface Route {
550575
loadChildren?: LoadChildren;
551576

552577
/**
553-
* Defines when guards and resolvers will be run. One of
554-
* - `paramsOrQueryParamsChange` : Run when query parameters change.
578+
* A policy for when to run guards and resolvers on a route.
579+
*
580+
* Guards and/or resolvers will always run when a route is activated or deactivated. When a route
581+
* is unchanged, the default behavior is the same as `paramsChange`.
582+
*
583+
* `paramsChange` : Rerun the guards and resolvers when path or
584+
* path param changes. This does not include query parameters. This option is the default.
555585
* - `always` : Run on every execution.
556-
* By default, guards and resolvers run only when the matrix
557-
* parameters of the route change.
586+
* - `pathParamsChange` : Rerun guards and resolvers when the path params
587+
* change. This does not compare matrix or query parameters.
588+
* - `paramsOrQueryParamsChange` : Run when path, matrix, or query parameters change.
589+
* - `pathParamsOrQueryParamsChange` : Rerun guards and resolvers when the path params
590+
* change or query params have changed. This does not include matrix parameters.
558591
*
559592
* @see RunGuardsAndResolvers
560593
*/
@@ -1194,6 +1227,17 @@ export type CanLoadFn = (route: Route, segments: UrlSegment[]) =>
11941227
* @publicApi
11951228
*/
11961229
export interface NavigationBehaviorOptions {
1230+
/**
1231+
* How to handle a navigation request to the current URL.
1232+
*
1233+
* This value is a subset of the options available in `OnSameUrlNavigation` and
1234+
* will take precedence over the default value set for the `Router`.
1235+
*
1236+
* @see `OnSameUrlNavigation`
1237+
* @see `RouterConfigOptions`
1238+
*/
1239+
onSameUrlNavigation?: Extract<OnSameUrlNavigation, 'reload'>;
1240+
11971241
/**
11981242
* When true, navigates without pushing a new state into history.
11991243
*

packages/router/src/navigation_transition.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,10 @@ export class NavigationTransitions {
344344
// try processing the URL again.
345345
browserUrlTree !== this.router.currentUrlTree.toString();
346346

347-
if (!urlTransition && this.router.onSameUrlNavigation !== 'reload') {
347+
348+
const onSameUrlNavigation =
349+
t.extras.onSameUrlNavigation ?? this.router.onSameUrlNavigation;
350+
if (!urlTransition && onSameUrlNavigation !== 'reload') {
348351
const reason = NG_DEV_MODE ?
349352
`Navigation to ${
350353
t.rawUrl} was ignored because it is the same as the current Router URL.` :

packages/router/src/router.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {BehaviorSubject, Observable, of, Subject, SubscriptionLike} from 'rxjs';
1313
import {createUrlTree} from './create_url_tree';
1414
import {RuntimeErrorCode} from './errors';
1515
import {Event, NavigationCancel, NavigationCancellationCode, NavigationEnd, NavigationTrigger, RouteConfigLoadEnd, RouteConfigLoadStart} from './events';
16-
import {NavigationBehaviorOptions, Route, Routes} from './models';
16+
import {NavigationBehaviorOptions, OnSameUrlNavigation, Route, Routes} from './models';
1717
import {Navigation, NavigationExtras, NavigationTransition, NavigationTransitions, RestoredState, UrlCreationOptions} from './navigation_transition';
1818
import {TitleStrategy} from './page_title_strategy';
1919
import {RouteReuseStrategy} from './route_reuse_strategy';
@@ -281,24 +281,15 @@ export class Router {
281281
titleStrategy?: TitleStrategy = inject(TitleStrategy);
282282

283283
/**
284-
* How to handle a navigation request to the current URL. One of:
284+
* How to handle a navigation request to the current URL.
285285
*
286-
* - `'ignore'` : The router ignores the request.
287-
* - `'reload'` : The router reloads the URL. Use to implement a "refresh" feature.
288-
*
289-
* Note that this only configures whether the Route reprocesses the URL and triggers related
290-
* action and events like redirects, guards, and resolvers. By default, the router re-uses a
291-
* component instance when it re-navigates to the same component type without visiting a different
292-
* component first. This behavior is configured by the `RouteReuseStrategy`. In order to reload
293-
* routed components on same url navigation, you need to set `onSameUrlNavigation` to `'reload'`
294-
* _and_ provide a `RouteReuseStrategy` which returns `false` for `shouldReuseRoute`.
295286
*
296287
* @deprecated Configure this through `provideRouter` or `RouterModule.forRoot` instead.
297288
* @see `withRouterConfig`
298289
* @see `provideRouter`
299290
* @see `RouterModule`
300291
*/
301-
onSameUrlNavigation: 'reload'|'ignore' = 'ignore';
292+
onSameUrlNavigation: OnSameUrlNavigation = 'ignore';
302293

303294
/**
304295
* How to merge parameters, data, resolved data, and title from parent to child

packages/router/src/router_config.ts

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

99
import {InjectionToken} from '@angular/core';
1010

11+
import {OnSameUrlNavigation} from './models';
1112
import {UrlSerializer, UrlTree} from './url_tree';
1213

1314
const NG_DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
@@ -74,13 +75,13 @@ export interface RouterConfigOptions {
7475
canceledNavigationResolution?: 'replace'|'computed';
7576

7677
/**
77-
* Define what the router should do if it receives a navigation request to the current URL.
78-
* Default is `ignore`, which causes the router ignores the navigation.
79-
* This can disable features such as a "refresh" button.
80-
* Use this option to configure the behavior when navigating to the
81-
* current URL. Default is 'ignore'.
78+
* Configures the default for handling a navigation request to the current URL.
79+
*
80+
* If unset, the `Router` will use `'ignore'`.
81+
*
82+
* @see `OnSameUrlNavigation`
8283
*/
83-
onSameUrlNavigation?: 'reload'|'ignore';
84+
onSameUrlNavigation?: OnSameUrlNavigation;
8485

8586
/**
8687
* Defines how the router merges parameters, data, and resolved data from parent to child

packages/router/test/integration.spec.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,18 @@ describe('Integration', () => {
6060
})));
6161

6262
describe('navigation', function() {
63-
it('should navigate to the current URL', fakeAsync(inject([Router], (router: Router) => {
64-
router.onSameUrlNavigation = 'reload';
63+
it('should navigate to the current URL', fakeAsync(() => {
64+
TestBed.configureTestingModule({
65+
providers: [
66+
provideRouter([], withRouterConfig({onSameUrlNavigation: 'reload'})),
67+
]
68+
});
69+
const router = TestBed.inject(Router);
6570
router.resetConfig([
6671
{path: '', component: SimpleCmp},
6772
{path: 'simple', component: SimpleCmp},
6873
]);
6974

70-
const fixture = createRoot(router, RootCmp);
7175
const events: Event[] = [];
7276
router.events.subscribe(e => onlyNavigationStartAndEnd(e) && events.push(e));
7377

@@ -81,8 +85,34 @@ describe('Integration', () => {
8185
[NavigationStart, '/simple'], [NavigationEnd, '/simple'], [NavigationStart, '/simple'],
8286
[NavigationEnd, '/simple']
8387
]);
84-
})));
88+
}));
8589

90+
it('should override default onSameUrlNavigation with extras', async () => {
91+
TestBed.configureTestingModule({
92+
providers: [
93+
provideRouter([], withRouterConfig({onSameUrlNavigation: 'ignore'})),
94+
]
95+
});
96+
const router = TestBed.inject(Router);
97+
router.resetConfig([
98+
{path: '', component: SimpleCmp},
99+
{path: 'simple', component: SimpleCmp},
100+
]);
101+
102+
const events: Event[] = [];
103+
router.events.subscribe(e => onlyNavigationStartAndEnd(e) && events.push(e));
104+
105+
await router.navigateByUrl('/simple');
106+
await router.navigateByUrl('/simple');
107+
// By default, the second navigation is ignored
108+
expectEvents(events, [[NavigationStart, '/simple'], [NavigationEnd, '/simple']]);
109+
await router.navigateByUrl('/simple', {onSameUrlNavigation: 'reload'});
110+
// We overrode the `onSameUrlNavigation` value. This navigation should be processed.
111+
expectEvents(events, [
112+
[NavigationStart, '/simple'], [NavigationEnd, '/simple'], [NavigationStart, '/simple'],
113+
[NavigationEnd, '/simple']
114+
]);
115+
});
86116

87117
it('should ignore empty paths in relative links',
88118
fakeAsync(inject([Router], (router: Router) => {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy