feat(router): add "onSameUrlNavigation" router configuration option (#19463)

PR Close #19463
This commit is contained in:
vsavkin 2017-10-19 18:32:50 -04:00 committed by Matias Niemelä
parent adab4f3e49
commit d3211a2468
4 changed files with 43 additions and 12 deletions

View File

@ -241,6 +241,14 @@ export class Router {
routeReuseStrategy: RouteReuseStrategy = new DefaultRouteReuseStrategy(); routeReuseStrategy: RouteReuseStrategy = new DefaultRouteReuseStrategy();
/**
* Define what the router should do if it receives a navigation request to the current URL.
* By default, the router will ignore this navigation. However, this prevents features such
* as a "refresh" button. Use this option to configure the behavior when navigating to the
* current URL. Default is 'ignore'.
*/
onSameUrlNavigation: 'reload'|'ignore' = 'ignore';
/** /**
* Creates the router service. * Creates the router service.
*/ */
@ -552,7 +560,8 @@ export class Router {
const url = this.urlHandlingStrategy.extract(rawUrl); const url = this.urlHandlingStrategy.extract(rawUrl);
const urlTransition = !this.navigated || url.toString() !== this.currentUrlTree.toString(); const urlTransition = !this.navigated || url.toString() !== this.currentUrlTree.toString();
if (this.urlHandlingStrategy.shouldProcessUrl(rawUrl)) { if ((this.onSameUrlNavigation === 'reload' ? true : urlTransition) &&
this.urlHandlingStrategy.shouldProcessUrl(rawUrl)) {
(this.events as Subject<Event>).next(new NavigationStart(id, this.serializeUrl(url))); (this.events as Subject<Event>).next(new NavigationStart(id, this.serializeUrl(url)));
Promise.resolve() Promise.resolve()
.then( .then(

View File

@ -129,12 +129,15 @@ export class RouterModule {
* Creates a module with all the router providers and directives. It also optionally sets up an * Creates a module with all the router providers and directives. It also optionally sets up an
* application listener to perform an initial navigation. * application listener to perform an initial navigation.
* *
* Options: * Options (see {@link ExtraOptions}):
* * `enableTracing` makes the router log all its internal events to the console. * * `enableTracing` makes the router log all its internal events to the console.
* * `useHash` enables the location strategy that uses the URL fragment instead of the history * * `useHash` enables the location strategy that uses the URL fragment instead of the history
* API. * API.
* * `initialNavigation` disables the initial navigation. * * `initialNavigation` disables the initial navigation.
* * `errorHandler` provides a custom error handler. * * `errorHandler` provides a custom error handler.
* * `preloadingStrategy` configures a preloading strategy (see {@link PreloadAllModules}).
* * `onSameUrlNavigation` configures how the router handles navigation to the current URL. See
* {@link ExtraOptions} for more details.
*/ */
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders { static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders {
return { return {
@ -267,6 +270,14 @@ export interface ExtraOptions {
* Configures a preloading strategy. See {@link PreloadAllModules}. * Configures a preloading strategy. See {@link PreloadAllModules}.
*/ */
preloadingStrategy?: any; preloadingStrategy?: any;
/**
* Define what the router should do if it receives a navigation request to the current URL.
* By default, the router will ignore this navigation. However, this prevents features such
* as a "refresh" button. Use this option to configure the behavior when navigating to the
* current URL. Default is 'ignore'.
*/
onSameUrlNavigation?: 'reload'|'ignore';
} }
export function setupRouter( export function setupRouter(
@ -299,6 +310,10 @@ export function setupRouter(
}); });
} }
if (opts.onSameUrlNavigation) {
router.onSameUrlNavigation = opts.onSameUrlNavigation;
}
return router; return router;
} }

View File

@ -53,6 +53,7 @@ describe('Integration', () => {
describe('navigation', function() { describe('navigation', function() {
it('should navigate to the current URL', fakeAsync(inject([Router], (router: Router) => { it('should navigate to the current URL', fakeAsync(inject([Router], (router: Router) => {
router.onSameUrlNavigation = 'reload';
router.resetConfig([ router.resetConfig([
{path: '', component: SimpleCmp}, {path: '', component: SimpleCmp},
{path: 'simple', component: SimpleCmp}, {path: 'simple', component: SimpleCmp},
@ -3568,30 +3569,34 @@ describe('Integration', () => {
}]); }]);
const events: any[] = []; const events: any[] = [];
router.events.subscribe(e => onlyNavigationStartAndEnd(e) && events.push(e)); router.events.subscribe(e => e instanceof RouterEvent && events.push(e));
location.go('/include/user/kate(aux:excluded)'); location.go('/include/user/kate(aux:excluded)');
advance(fixture); advance(fixture);
expect(location.path()).toEqual('/include/user/kate(aux:excluded)'); expect(location.path()).toEqual('/include/user/kate(aux:excluded)');
expectEvents( expectEvents(events, [
events, [NavigationStart, '/include/user/kate'], [RoutesRecognized, '/include/user/kate'],
[[NavigationStart, '/include/user/kate'], [NavigationEnd, '/include/user/kate']]); [GuardsCheckStart, '/include/user/kate'], [GuardsCheckEnd, '/include/user/kate'],
[ResolveStart, '/include/user/kate'], [ResolveEnd, '/include/user/kate'],
[NavigationEnd, '/include/user/kate']
]);
events.splice(0); events.splice(0);
location.go('/include/user/kate(aux:excluded2)'); location.go('/include/user/kate(aux:excluded2)');
advance(fixture); advance(fixture);
expectEvents( expectEvents(events, []);
events,
[[NavigationStart, '/include/user/kate'], [NavigationEnd, '/include/user/kate']]);
events.splice(0);
router.navigateByUrl('/include/simple'); router.navigateByUrl('/include/simple');
advance(fixture); advance(fixture);
expect(location.path()).toEqual('/include/simple(aux:excluded2)'); expect(location.path()).toEqual('/include/simple(aux:excluded2)');
expectEvents( expectEvents(events, [
events, [[NavigationStart, '/include/simple'], [NavigationEnd, '/include/simple']]); [NavigationStart, '/include/simple'], [RoutesRecognized, '/include/simple'],
[GuardsCheckStart, '/include/simple'], [GuardsCheckEnd, '/include/simple'],
[ResolveStart, '/include/simple'], [ResolveEnd, '/include/simple'],
[NavigationEnd, '/include/simple']
]);
}))); })));
}); });
}); });

View File

@ -126,6 +126,7 @@ export interface ExtraOptions {
enableTracing?: boolean; enableTracing?: boolean;
errorHandler?: ErrorHandler; errorHandler?: ErrorHandler;
initialNavigation?: InitialNavigation; initialNavigation?: InitialNavigation;
onSameUrlNavigation?: 'reload' | 'ignore';
preloadingStrategy?: any; preloadingStrategy?: any;
useHash?: boolean; useHash?: boolean;
} }
@ -327,6 +328,7 @@ export declare class Router {
errorHandler: ErrorHandler; errorHandler: ErrorHandler;
readonly events: Observable<Event>; readonly events: Observable<Event>;
navigated: boolean; navigated: boolean;
onSameUrlNavigation: 'reload' | 'ignore';
routeReuseStrategy: RouteReuseStrategy; routeReuseStrategy: RouteReuseStrategy;
readonly routerState: RouterState; readonly routerState: RouterState;
readonly url: string; readonly url: string;