mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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
This commit is contained in:
@@ -379,6 +379,7 @@ export interface Navigation {
|
||||
|
||||
// @public
|
||||
export interface NavigationBehaviorOptions {
|
||||
onSameUrlNavigation?: Extract<OnSameUrlNavigation, 'reload'>;
|
||||
replaceUrl?: boolean;
|
||||
skipLocationChange?: boolean;
|
||||
state?: {
|
||||
@@ -493,6 +494,9 @@ export class NoPreloading implements PreloadingStrategy {
|
||||
static ɵprov: i0.ɵɵInjectableDeclaration<NoPreloading>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type OnSameUrlNavigation = 'reload' | 'ignore';
|
||||
|
||||
// @public
|
||||
export class OutletContext {
|
||||
// (undocumented)
|
||||
@@ -677,7 +681,7 @@ export class Router {
|
||||
// (undocumented)
|
||||
ngOnDestroy(): void;
|
||||
// @deprecated
|
||||
onSameUrlNavigation: 'reload' | 'ignore';
|
||||
onSameUrlNavigation: OnSameUrlNavigation;
|
||||
// @deprecated
|
||||
paramsInheritanceStrategy: 'emptyOnly' | 'always';
|
||||
parseUrl(url: string): UrlTree;
|
||||
@@ -709,7 +713,7 @@ export const ROUTER_INITIALIZER: InjectionToken<(compRef: ComponentRef<any>) =>
|
||||
// @public
|
||||
export interface RouterConfigOptions {
|
||||
canceledNavigationResolution?: 'replace' | 'computed';
|
||||
onSameUrlNavigation?: 'reload' | 'ignore';
|
||||
onSameUrlNavigation?: OnSameUrlNavigation;
|
||||
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
|
||||
urlUpdateStrategy?: 'deferred' | 'eager';
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
||||
export {RouterLinkActive} from './directives/router_link_active';
|
||||
export {RouterOutlet, RouterOutletContract} from './directives/router_outlet';
|
||||
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';
|
||||
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';
|
||||
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';
|
||||
export {Navigation, NavigationExtras, UrlCreationOptions} from './navigation_transition';
|
||||
export {DefaultTitleStrategy, TitleStrategy} from './page_title_strategy';
|
||||
export {DebugTracingFeature, DisabledInitialNavigationFeature, EnabledBlockingInitialNavigationFeature, InitialNavigationFeature, InMemoryScrollingFeature, PreloadingFeature, provideRouter, provideRoutes, RouterConfigurationFeature, RouterFeature, RouterFeatures, withDebugTracing, withDisabledInitialNavigation, withEnabledBlockingInitialNavigation, withInMemoryScrolling, withPreloading, withRouterConfig} from './provide_router';
|
||||
|
||||
@@ -13,6 +13,31 @@ import {DeprecatedLoadChildren} from './deprecated_load_children';
|
||||
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
||||
|
||||
/**
|
||||
* How to handle a navigation request to the current URL. One of:
|
||||
*
|
||||
* - `'ignore'` : The router ignores the request it is the same as the current state.
|
||||
* - `'reload'` : The router processes the URL even if it is not different from the current state.
|
||||
* One example of when you might want this option is if a `canMatch` guard depends on
|
||||
* application state and initially rejects navigation to a route. After fixing the state, you want
|
||||
* to re-navigate to the same URL so the route with the `canMatch` guard can activate.
|
||||
*
|
||||
* Note that this only configures whether the Route reprocesses the URL and triggers related
|
||||
* action and events like redirects, guards, and resolvers. By default, the router re-uses a
|
||||
* component instance when it re-navigates to the same component type without visiting a different
|
||||
* component first. This behavior is configured by the `RouteReuseStrategy`. In order to reload
|
||||
* routed components on same url navigation, you need to set `onSameUrlNavigation` to `'reload'`
|
||||
* _and_ provide a `RouteReuseStrategy` which returns `false` for `shouldReuseRoute`. Additionally,
|
||||
* resolvers and most guards for routes do not run unless the path or path params changed
|
||||
* (configured by `runGuardsAndResolvers`).
|
||||
*
|
||||
* @publicApi
|
||||
* @see RouteReuseStrategy
|
||||
* @see RunGuardsAndResolvers
|
||||
* @see NavigationBehaviorOptions
|
||||
* @see RouterConfigOptions
|
||||
*/
|
||||
export type OnSameUrlNavigation = 'reload'|'ignore';
|
||||
|
||||
/**
|
||||
* Represents a route configuration for the Router service.
|
||||
@@ -550,11 +575,19 @@ export interface Route {
|
||||
loadChildren?: LoadChildren;
|
||||
|
||||
/**
|
||||
* Defines when guards and resolvers will be run. One of
|
||||
* - `paramsOrQueryParamsChange` : Run when query parameters change.
|
||||
* A policy for when to run guards and resolvers on a route.
|
||||
*
|
||||
* Guards and/or resolvers will always run when a route is activated or deactivated. When a route
|
||||
* is unchanged, the default behavior is the same as `paramsChange`.
|
||||
*
|
||||
* `paramsChange` : Rerun the guards and resolvers when path or
|
||||
* path param changes. This does not include query parameters. This option is the default.
|
||||
* - `always` : Run on every execution.
|
||||
* By default, guards and resolvers run only when the matrix
|
||||
* parameters of the route change.
|
||||
* - `pathParamsChange` : Rerun guards and resolvers when the path params
|
||||
* change. This does not compare matrix or query parameters.
|
||||
* - `paramsOrQueryParamsChange` : Run when path, matrix, or query parameters change.
|
||||
* - `pathParamsOrQueryParamsChange` : Rerun guards and resolvers when the path params
|
||||
* change or query params have changed. This does not include matrix parameters.
|
||||
*
|
||||
* @see RunGuardsAndResolvers
|
||||
*/
|
||||
@@ -1194,6 +1227,17 @@ export type CanLoadFn = (route: Route, segments: UrlSegment[]) =>
|
||||
* @publicApi
|
||||
*/
|
||||
export interface NavigationBehaviorOptions {
|
||||
/**
|
||||
* How to handle a navigation request to the current URL.
|
||||
*
|
||||
* This value is a subset of the options available in `OnSameUrlNavigation` and
|
||||
* will take precedence over the default value set for the `Router`.
|
||||
*
|
||||
* @see `OnSameUrlNavigation`
|
||||
* @see `RouterConfigOptions`
|
||||
*/
|
||||
onSameUrlNavigation?: Extract<OnSameUrlNavigation, 'reload'>;
|
||||
|
||||
/**
|
||||
* When true, navigates without pushing a new state into history.
|
||||
*
|
||||
|
||||
@@ -344,7 +344,10 @@ export class NavigationTransitions {
|
||||
// try processing the URL again.
|
||||
browserUrlTree !== this.router.currentUrlTree.toString();
|
||||
|
||||
if (!urlTransition && this.router.onSameUrlNavigation !== 'reload') {
|
||||
|
||||
const onSameUrlNavigation =
|
||||
t.extras.onSameUrlNavigation ?? this.router.onSameUrlNavigation;
|
||||
if (!urlTransition && onSameUrlNavigation !== 'reload') {
|
||||
const reason = NG_DEV_MODE ?
|
||||
`Navigation to ${
|
||||
t.rawUrl} was ignored because it is the same as the current Router URL.` :
|
||||
|
||||
@@ -13,7 +13,7 @@ import {BehaviorSubject, Observable, of, Subject, SubscriptionLike} from 'rxjs';
|
||||
import {createUrlTree} from './create_url_tree';
|
||||
import {RuntimeErrorCode} from './errors';
|
||||
import {Event, NavigationCancel, NavigationCancellationCode, NavigationEnd, NavigationTrigger, RouteConfigLoadEnd, RouteConfigLoadStart} from './events';
|
||||
import {NavigationBehaviorOptions, Route, Routes} from './models';
|
||||
import {NavigationBehaviorOptions, OnSameUrlNavigation, Route, Routes} from './models';
|
||||
import {Navigation, NavigationExtras, NavigationTransition, NavigationTransitions, RestoredState, UrlCreationOptions} from './navigation_transition';
|
||||
import {TitleStrategy} from './page_title_strategy';
|
||||
import {RouteReuseStrategy} from './route_reuse_strategy';
|
||||
@@ -281,24 +281,15 @@ export class Router {
|
||||
titleStrategy?: TitleStrategy = inject(TitleStrategy);
|
||||
|
||||
/**
|
||||
* How to handle a navigation request to the current URL. One of:
|
||||
* How to handle a navigation request to the current URL.
|
||||
*
|
||||
* - `'ignore'` : The router ignores the request.
|
||||
* - `'reload'` : The router reloads the URL. Use to implement a "refresh" feature.
|
||||
*
|
||||
* Note that this only configures whether the Route reprocesses the URL and triggers related
|
||||
* action and events like redirects, guards, and resolvers. By default, the router re-uses a
|
||||
* component instance when it re-navigates to the same component type without visiting a different
|
||||
* component first. This behavior is configured by the `RouteReuseStrategy`. In order to reload
|
||||
* routed components on same url navigation, you need to set `onSameUrlNavigation` to `'reload'`
|
||||
* _and_ provide a `RouteReuseStrategy` which returns `false` for `shouldReuseRoute`.
|
||||
*
|
||||
* @deprecated Configure this through `provideRouter` or `RouterModule.forRoot` instead.
|
||||
* @see `withRouterConfig`
|
||||
* @see `provideRouter`
|
||||
* @see `RouterModule`
|
||||
*/
|
||||
onSameUrlNavigation: 'reload'|'ignore' = 'ignore';
|
||||
onSameUrlNavigation: OnSameUrlNavigation = 'ignore';
|
||||
|
||||
/**
|
||||
* How to merge parameters, data, resolved data, and title from parent to child
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import {InjectionToken} from '@angular/core';
|
||||
|
||||
import {OnSameUrlNavigation} from './models';
|
||||
import {UrlSerializer, UrlTree} from './url_tree';
|
||||
|
||||
const NG_DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
||||
@@ -74,13 +75,13 @@ export interface RouterConfigOptions {
|
||||
canceledNavigationResolution?: 'replace'|'computed';
|
||||
|
||||
/**
|
||||
* Define what the router should do if it receives a navigation request to the current URL.
|
||||
* Default is `ignore`, which causes the router ignores the navigation.
|
||||
* This can disable features such as a "refresh" button.
|
||||
* Use this option to configure the behavior when navigating to the
|
||||
* current URL. Default is 'ignore'.
|
||||
* Configures the default for handling a navigation request to the current URL.
|
||||
*
|
||||
* If unset, the `Router` will use `'ignore'`.
|
||||
*
|
||||
* @see `OnSameUrlNavigation`
|
||||
*/
|
||||
onSameUrlNavigation?: 'reload'|'ignore';
|
||||
onSameUrlNavigation?: OnSameUrlNavigation;
|
||||
|
||||
/**
|
||||
* Defines how the router merges parameters, data, and resolved data from parent to child
|
||||
|
||||
@@ -60,14 +60,18 @@ describe('Integration', () => {
|
||||
})));
|
||||
|
||||
describe('navigation', function() {
|
||||
it('should navigate to the current URL', fakeAsync(inject([Router], (router: Router) => {
|
||||
router.onSameUrlNavigation = 'reload';
|
||||
it('should navigate to the current URL', fakeAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
provideRouter([], withRouterConfig({onSameUrlNavigation: 'reload'})),
|
||||
]
|
||||
});
|
||||
const router = TestBed.inject(Router);
|
||||
router.resetConfig([
|
||||
{path: '', component: SimpleCmp},
|
||||
{path: 'simple', component: SimpleCmp},
|
||||
]);
|
||||
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
const events: Event[] = [];
|
||||
router.events.subscribe(e => onlyNavigationStartAndEnd(e) && events.push(e));
|
||||
|
||||
@@ -81,8 +85,34 @@ describe('Integration', () => {
|
||||
[NavigationStart, '/simple'], [NavigationEnd, '/simple'], [NavigationStart, '/simple'],
|
||||
[NavigationEnd, '/simple']
|
||||
]);
|
||||
})));
|
||||
}));
|
||||
|
||||
it('should override default onSameUrlNavigation with extras', async () => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
provideRouter([], withRouterConfig({onSameUrlNavigation: 'ignore'})),
|
||||
]
|
||||
});
|
||||
const router = TestBed.inject(Router);
|
||||
router.resetConfig([
|
||||
{path: '', component: SimpleCmp},
|
||||
{path: 'simple', component: SimpleCmp},
|
||||
]);
|
||||
|
||||
const events: Event[] = [];
|
||||
router.events.subscribe(e => onlyNavigationStartAndEnd(e) && events.push(e));
|
||||
|
||||
await router.navigateByUrl('/simple');
|
||||
await router.navigateByUrl('/simple');
|
||||
// By default, the second navigation is ignored
|
||||
expectEvents(events, [[NavigationStart, '/simple'], [NavigationEnd, '/simple']]);
|
||||
await router.navigateByUrl('/simple', {onSameUrlNavigation: 'reload'});
|
||||
// We overrode the `onSameUrlNavigation` value. This navigation should be processed.
|
||||
expectEvents(events, [
|
||||
[NavigationStart, '/simple'], [NavigationEnd, '/simple'], [NavigationStart, '/simple'],
|
||||
[NavigationEnd, '/simple']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore empty paths in relative links',
|
||||
fakeAsync(inject([Router], (router: Router) => {
|
||||
|
||||
Reference in New Issue
Block a user