From 12c317603a902346c72ee6f13a0e21c5b45869ed Mon Sep 17 00:00:00 2001 From: Jason Aden Date: Fri, 14 Dec 2018 14:02:50 -0500 Subject: [PATCH] feat(router): add predicate function mode for runGuardsAndResolvers (#27682) This option means guards and resolvers will ignore changes when a provided predicate function returns `false`. This supports use cases where an application needs to ignore some param updates but not others. For example, changing a sort param in the URL might need to be ignored, whereas changing the a `project` param might require re-run of guards and resolvers. Related to #26861 #18253 #27464 PR Close #27682 --- packages/router/src/config.ts | 10 +++++- packages/router/src/utils/preactivation.ts | 3 ++ packages/router/test/integration.spec.ts | 37 ++++++++++++++++++++++ tools/public_api_guard/router/router.d.ts | 2 +- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/router/src/config.ts b/packages/router/src/config.ts index 8f989b5664..f60c3dff45 100644 --- a/packages/router/src/config.ts +++ b/packages/router/src/config.ts @@ -8,10 +8,13 @@ import {NgModuleFactory, NgModuleRef, Type} from '@angular/core'; import {Observable} from 'rxjs'; + import {EmptyOutletComponent} from './components/empty_outlet'; +import {ActivatedRouteSnapshot} from './router_state'; import {PRIMARY_OUTLET} from './shared'; import {UrlSegment, UrlSegmentGroup} from './url_tree'; + /** * @description * @@ -48,6 +51,10 @@ import {UrlSegment, UrlSegmentGroup} from './url_tree'; * - `pathParamsOrQueryParamsChange` - Same as `pathParamsChange`, but also rerun when any query * param changes * - `always` - Run guards and resolvers on every navigation. + * - (from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean - Use a predicate + * function when none of the pre-configured modes fit the needs of the application. An example + * might be when you need to ignore updates to a param such as `sortDirection`, but need to + * reload guards and resolvers when changing the `searchRoot` param. * - `children` is an array of child route definitions. * - `loadChildren` is a reference to lazy loaded child routes. See `LoadChildren` for more * info. @@ -369,7 +376,8 @@ export type QueryParamsHandling = 'merge' | 'preserve' | ''; * @publicApi */ export type RunGuardsAndResolvers = 'pathParamsChange' | 'pathParamsOrQueryParamsChange' | - 'paramsChange' | 'paramsOrQueryParamsChange' | 'always'; + 'paramsChange' | 'paramsOrQueryParamsChange' | 'always' | + ((from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean); /** * See `Routes` for more details. diff --git a/packages/router/src/utils/preactivation.ts b/packages/router/src/utils/preactivation.ts index 90d3a9bcc8..1899a0575b 100644 --- a/packages/router/src/utils/preactivation.ts +++ b/packages/router/src/utils/preactivation.ts @@ -147,6 +147,9 @@ function getRouteGuards( function shouldRunGuardsAndResolvers( curr: ActivatedRouteSnapshot, future: ActivatedRouteSnapshot, mode: RunGuardsAndResolvers | undefined): boolean { + if (typeof mode === 'function') { + return mode(curr, future); + } switch (mode) { case 'pathParamsChange': return !equalPath(curr.url, future.url); diff --git a/packages/router/test/integration.spec.ts b/packages/router/test/integration.spec.ts index b99df12717..2b2b83fd54 100644 --- a/packages/router/test/integration.spec.ts +++ b/packages/router/test/integration.spec.ts @@ -2487,6 +2487,43 @@ describe('Integration', () => { expect(guardRunCount).toEqual(3); expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}]); }))); + + it('should allow a predicate function to determine when to run guards and resolvers', + fakeAsync(inject([Router], (router: Router) => { + const fixture = configureRouter(router, (from, to) => to.paramMap.get('p') === '2'); + + 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}]); + + // Adding `p` param shouldn't cause re-run + router.navigateByUrl('/a;p=1'); + advance(fixture); + expect(guardRunCount).toEqual(1); + expect(recordedData).toEqual([{data: 0}]); + + // Re-run should trigger on p=2 + router.navigateByUrl('/a;p=2'); + advance(fixture); + expect(guardRunCount).toEqual(2); + expect(recordedData).toEqual([{data: 0}, {data: 1}]); + + // Any other changes don't pass the predicate + router.navigateByUrl('/a;p=3?q=1'); + advance(fixture); + expect(guardRunCount).toEqual(2); + expect(recordedData).toEqual([{data: 0}, {data: 1}]); + + // Changing query params will re-run guards/resolvers + router.navigateByUrl('/a;p=3?q=2'); + advance(fixture); + expect(guardRunCount).toEqual(2); + expect(recordedData).toEqual([{data: 0}, {data: 1}]); + }))); }); describe('should wait for parent to complete', () => { diff --git a/tools/public_api_guard/router/router.d.ts b/tools/public_api_guard/router/router.d.ts index 49715128e5..24231d1ea6 100644 --- a/tools/public_api_guard/router/router.d.ts +++ b/tools/public_api_guard/router/router.d.ts @@ -473,7 +473,7 @@ export declare class RoutesRecognized extends RouterEvent { toString(): string; } -export declare type RunGuardsAndResolvers = 'pathParamsChange' | 'pathParamsOrQueryParamsChange' | 'paramsChange' | 'paramsOrQueryParamsChange' | 'always'; +export declare type RunGuardsAndResolvers = 'pathParamsChange' | 'pathParamsOrQueryParamsChange' | 'paramsChange' | 'paramsOrQueryParamsChange' | 'always' | ((from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean); export declare class Scroll { readonly anchor: string | null;