feat(router): add pathParamsChange mode for runGuardsAndResolvers (#26861)

This option means guards and resolvers will ignore changes to optional
parameters such as query and matrix params. When the path or any path
params change, guards and resolvers will be run

Related to discussion in #18253
FW-560 #resolve

PR Close #26861
This commit is contained in:
Jason Aden 2018-10-30 14:13:55 -07:00 committed by Andrew Kushnir
parent 3da82338d1
commit bf6ac6cef8
4 changed files with 72 additions and 5 deletions

View File

@ -37,8 +37,15 @@ import {UrlSegment, UrlSegmentGroup} from './url_tree';
* - `resolve` is a map of DI tokens used to look up data resolvers. See `Resolve` for more
* info.
* - `runGuardsAndResolvers` defines when guards and resolvers will be run. By default they run only
* when the matrix parameters of the route change. When set to `paramsOrQueryParamsChange` they
* will also run when query params change. And when set to `always`, they will run every time.
* when the matrix parameters of the route change. Options include:
* - `paramsChange` (default) - Run guards and resolvers when path or matrix params change. This
* mode ignores query param changes.
* - `paramsOrQueryParamsChange` - Guards and resolvers will run when any parameters change. This
* includes path, matrix, and query params.
* - `pathParamsChange` Run guards and resolvers path or any path params change. This mode is
* useful if you want to ignore changes to all optional parameters such as query *and* matrix
* params.
* - `always` - Run guards and resolvers on every navigation.
* - `children` is an array of child route definitions.
* - `loadChildren` is a reference to lazy loaded child routes. See `LoadChildren` for more
* info.
@ -359,7 +366,8 @@ export type QueryParamsHandling = 'merge' | 'preserve' | '';
* See `Routes` for more details.
* @publicApi
*/
export type RunGuardsAndResolvers = 'paramsChange' | 'paramsOrQueryParamsChange' | 'always';
export type RunGuardsAndResolvers =
'pathParamsChange' | 'paramsChange' | 'paramsOrQueryParamsChange' | 'always';
/**
* See `Routes` for more details.

View File

@ -11,6 +11,7 @@ import {Injector} from '@angular/core';
import {LoadedRouterConfig, RunGuardsAndResolvers} from '../config';
import {ChildrenOutletContexts, OutletContext} from '../router_outlet_context';
import {ActivatedRouteSnapshot, RouterStateSnapshot, equalParamsAndUrlSegments} from '../router_state';
import {equalPath} from '../url_tree';
import {forEach, shallowEqual} from '../utils/collection';
import {TreeNode, nodeChildrenAsMap} from '../utils/tree';
@ -147,6 +148,9 @@ function shouldRunGuardsAndResolvers(
curr: ActivatedRouteSnapshot, future: ActivatedRouteSnapshot,
mode: RunGuardsAndResolvers | undefined): boolean {
switch (mode) {
case 'pathParamsChange':
return !equalPath(curr.url, future.url);
case 'always':
return true;

View File

@ -2138,7 +2138,13 @@ describe('Integration', () => {
canActivate: ['guard'],
resolve: {data: 'resolver'}
},
{path: 'b', component: SimpleCmp, outlet: 'right'}
{path: 'b', component: SimpleCmp, outlet: 'right'}, {
path: 'c/:param',
runGuardsAndResolvers,
component: RouteCmp,
canActivate: ['guard'],
resolve: {data: 'resolver'}
}
]);
router.navigateByUrl('/a');
@ -2236,6 +2242,55 @@ describe('Integration', () => {
expect(guardRunCount).toEqual(5);
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}, {data: 3}, {data: 4}]);
})));
it('should not rerun guards and resolvers', fakeAsync(inject([Router], (router: Router) => {
const fixture = configureRouter(router, 'pathParamsChange');
const cmp: RouteCmp = fixture.debugElement.children[1].componentInstance;
const recordedData: any[] = [];
cmp.route.data.subscribe((data: any) => recordedData.push(data));
// First navigation has already run
expect(guardRunCount).toEqual(1);
expect(recordedData).toEqual([{data: 0}]);
// Changing any optional params will not result in running guards or resolvers
router.navigateByUrl('/a;p=1');
advance(fixture);
expect(guardRunCount).toEqual(1);
expect(recordedData).toEqual([{data: 0}]);
router.navigateByUrl('/a;p=2');
advance(fixture);
expect(guardRunCount).toEqual(1);
expect(recordedData).toEqual([{data: 0}]);
router.navigateByUrl('/a;p=2?q=1');
advance(fixture);
expect(guardRunCount).toEqual(1);
expect(recordedData).toEqual([{data: 0}]);
router.navigateByUrl('/a;p=2(right:b)?q=1');
advance(fixture);
expect(guardRunCount).toEqual(1);
expect(recordedData).toEqual([{data: 0}]);
// Change to new route with path param should run guards and resolvers
router.navigateByUrl('/c/paramValue');
advance(fixture);
expect(guardRunCount).toEqual(2);
// Modifying a path param should run guards and resolvers
router.navigateByUrl('/c/paramValueChanged');
advance(fixture);
expect(guardRunCount).toEqual(3);
// Adding optional params should not cause guards/resolvers to run
router.navigateByUrl('/c/paramValueChanged;p=1?q=2');
advance(fixture);
expect(guardRunCount).toEqual(3);
})));
});
describe('should wait for parent to complete', () => {

View File

@ -451,7 +451,7 @@ export declare class RoutesRecognized extends RouterEvent {
toString(): string;
}
export declare type RunGuardsAndResolvers = 'paramsChange' | 'paramsOrQueryParamsChange' | 'always';
export declare type RunGuardsAndResolvers = 'pathParamsChange' | 'paramsChange' | 'paramsOrQueryParamsChange' | 'always';
export declare class Scroll {
readonly anchor: string | null;