feat(router): add "paramsInheritanceStrategy" router configuration option
Previously, the router would merge path and matrix params, as well as data/resolve, with special rules (only merging down when the route has an empty path, or is component-less). This change adds an extra option "paramsInheritanceStrategy" which, when set to 'always', makes child routes unconditionally inherit params from parent routes. Closes #20572.
This commit is contained in:
		
							parent
							
								
									5f23a1223f
								
							
						
					
					
						commit
						5efea2f6a0
					
				| @ -21,7 +21,7 @@ import {reduce} from 'rxjs/operator/reduce'; | |||||||
| import {LoadedRouterConfig, ResolveData, RunGuardsAndResolvers} from './config'; | import {LoadedRouterConfig, ResolveData, RunGuardsAndResolvers} from './config'; | ||||||
| import {ActivationStart, ChildActivationStart, Event} from './events'; | import {ActivationStart, ChildActivationStart, Event} from './events'; | ||||||
| import {ChildrenOutletContexts, OutletContext} from './router_outlet_context'; | import {ChildrenOutletContexts, OutletContext} from './router_outlet_context'; | ||||||
| import {ActivatedRouteSnapshot, RouterStateSnapshot, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state'; | import {ActivatedRouteSnapshot, ParamsInheritanceStrategy, RouterStateSnapshot, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state'; | ||||||
| import {andObservables, forEach, shallowEqual, wrapIntoObservable} from './utils/collection'; | import {andObservables, forEach, shallowEqual, wrapIntoObservable} from './utils/collection'; | ||||||
| import {TreeNode, nodeChildrenAsMap} from './utils/tree'; | import {TreeNode, nodeChildrenAsMap} from './utils/tree'; | ||||||
| 
 | 
 | ||||||
| @ -63,11 +63,11 @@ export class PreActivation { | |||||||
|         (canDeactivate: boolean) => canDeactivate ? this.runCanActivateChecks() : of (false)); |         (canDeactivate: boolean) => canDeactivate ? this.runCanActivateChecks() : of (false)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   resolveData(): Observable<any> { |   resolveData(paramsInheritanceStrategy: ParamsInheritanceStrategy): Observable<any> { | ||||||
|     if (!this.isActivating()) return of (null); |     if (!this.isActivating()) return of (null); | ||||||
|     const checks$ = from(this.canActivateChecks); |     const checks$ = from(this.canActivateChecks); | ||||||
|     const runningChecks$ = |     const runningChecks$ = concatMap.call( | ||||||
|         concatMap.call(checks$, (check: CanActivate) => this.runResolve(check.route)); |         checks$, (check: CanActivate) => this.runResolve(check.route, paramsInheritanceStrategy)); | ||||||
|     return reduce.call(runningChecks$, (_: any, __: any) => _); |     return reduce.call(runningChecks$, (_: any, __: any) => _); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -306,11 +306,14 @@ export class PreActivation { | |||||||
|     return every.call(canDeactivate$, (result: any) => result === true); |     return every.call(canDeactivate$, (result: any) => result === true); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private runResolve(future: ActivatedRouteSnapshot): Observable<any> { |   private runResolve( | ||||||
|  |       future: ActivatedRouteSnapshot, | ||||||
|  |       paramsInheritanceStrategy: ParamsInheritanceStrategy): Observable<any> { | ||||||
|     const resolve = future._resolve; |     const resolve = future._resolve; | ||||||
|     return map.call(this.resolveNode(resolve, future), (resolvedData: any): any => { |     return map.call(this.resolveNode(resolve, future), (resolvedData: any): any => { | ||||||
|       future._resolvedData = resolvedData; |       future._resolvedData = resolvedData; | ||||||
|       future.data = {...future.data, ...inheritedParamsDataResolve(future).resolve}; |       future.data = {...future.data, | ||||||
|  |                      ...inheritedParamsDataResolve(future, paramsInheritanceStrategy).resolve}; | ||||||
|       return null; |       return null; | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ import {Observer} from 'rxjs/Observer'; | |||||||
| import {of } from 'rxjs/observable/of'; | import {of } from 'rxjs/observable/of'; | ||||||
| 
 | 
 | ||||||
| import {Data, ResolveData, Route, Routes} from './config'; | import {Data, ResolveData, Route, Routes} from './config'; | ||||||
| import {ActivatedRouteSnapshot, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state'; | import {ActivatedRouteSnapshot, ParamsInheritanceStrategy, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state'; | ||||||
| import {PRIMARY_OUTLET, defaultUrlMatcher} from './shared'; | import {PRIMARY_OUTLET, defaultUrlMatcher} from './shared'; | ||||||
| import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree'; | import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree'; | ||||||
| import {forEach, last} from './utils/collection'; | import {forEach, last} from './utils/collection'; | ||||||
| @ -21,15 +21,17 @@ import {TreeNode} from './utils/tree'; | |||||||
| class NoMatch {} | class NoMatch {} | ||||||
| 
 | 
 | ||||||
| export function recognize( | export function recognize( | ||||||
|     rootComponentType: Type<any>| null, config: Routes, urlTree: UrlTree, |     rootComponentType: Type<any>| null, config: Routes, urlTree: UrlTree, url: string, | ||||||
|     url: string): Observable<RouterStateSnapshot> { |     paramsInheritanceStrategy: ParamsInheritanceStrategy = | ||||||
|   return new Recognizer(rootComponentType, config, urlTree, url).recognize(); |         'emptyOnly'): Observable<RouterStateSnapshot> { | ||||||
|  |   return new Recognizer(rootComponentType, config, urlTree, url, paramsInheritanceStrategy) | ||||||
|  |       .recognize(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class Recognizer { | class Recognizer { | ||||||
|   constructor( |   constructor( | ||||||
|       private rootComponentType: Type<any>|null, private config: Routes, private urlTree: UrlTree, |       private rootComponentType: Type<any>|null, private config: Routes, private urlTree: UrlTree, | ||||||
|       private url: string) {} |       private url: string, private paramsInheritanceStrategy: ParamsInheritanceStrategy) {} | ||||||
| 
 | 
 | ||||||
|   recognize(): Observable<RouterStateSnapshot> { |   recognize(): Observable<RouterStateSnapshot> { | ||||||
|     try { |     try { | ||||||
| @ -55,7 +57,7 @@ class Recognizer { | |||||||
|   inheritParamsAndData(routeNode: TreeNode<ActivatedRouteSnapshot>): void { |   inheritParamsAndData(routeNode: TreeNode<ActivatedRouteSnapshot>): void { | ||||||
|     const route = routeNode.value; |     const route = routeNode.value; | ||||||
| 
 | 
 | ||||||
|     const i = inheritedParamsDataResolve(route); |     const i = inheritedParamsDataResolve(route, this.paramsInheritanceStrategy); | ||||||
|     route.params = Object.freeze(i.params); |     route.params = Object.freeze(i.params); | ||||||
|     route.data = Object.freeze(i.data); |     route.data = Object.freeze(i.data); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ import {recognize} from './recognize'; | |||||||
| import {DefaultRouteReuseStrategy, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy'; | import {DefaultRouteReuseStrategy, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy'; | ||||||
| import {RouterConfigLoader} from './router_config_loader'; | import {RouterConfigLoader} from './router_config_loader'; | ||||||
| import {ChildrenOutletContexts} from './router_outlet_context'; | import {ChildrenOutletContexts} from './router_outlet_context'; | ||||||
| import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state'; | import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState, inheritedParamsDataResolve} from './router_state'; | ||||||
| import {Params, isNavigationCancelingError} from './shared'; | import {Params, isNavigationCancelingError} from './shared'; | ||||||
| import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy'; | import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy'; | ||||||
| import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree'; | import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree'; | ||||||
| @ -249,6 +249,16 @@ export class Router { | |||||||
|    */ |    */ | ||||||
|   onSameUrlNavigation: 'reload'|'ignore' = 'ignore'; |   onSameUrlNavigation: 'reload'|'ignore' = 'ignore'; | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * Defines how the router merges params, data and resolved data from parent to child | ||||||
|  |    * routes. Available options are: | ||||||
|  |    * | ||||||
|  |    * - `'emptyOnly'`, the default, only inherits parent params for path-less or component-less | ||||||
|  |    *   routes. | ||||||
|  |    * - `'always'`, enables unconditional inheritance of parent params. | ||||||
|  |    */ | ||||||
|  |   paramsInheritanceStrategy: 'emptyOnly'|'always' = 'emptyOnly'; | ||||||
|  | 
 | ||||||
|   /** |   /** | ||||||
|    * Creates the router service. |    * Creates the router service. | ||||||
|    */ |    */ | ||||||
| @ -611,7 +621,8 @@ export class Router { | |||||||
|         urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => { |         urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => { | ||||||
|           return map.call( |           return map.call( | ||||||
|               recognize( |               recognize( | ||||||
|                   this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl)), |                   this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl), | ||||||
|  |                   this.paramsInheritanceStrategy), | ||||||
|               (snapshot: any) => { |               (snapshot: any) => { | ||||||
| 
 | 
 | ||||||
|                 (this.events as Subject<Event>) |                 (this.events as Subject<Event>) | ||||||
| @ -667,7 +678,7 @@ export class Router { | |||||||
|             if (p.shouldActivate && preActivation.isActivating()) { |             if (p.shouldActivate && preActivation.isActivating()) { | ||||||
|               this.triggerEvent( |               this.triggerEvent( | ||||||
|                   new ResolveStart(id, this.serializeUrl(url), p.appliedUrl, p.snapshot)); |                   new ResolveStart(id, this.serializeUrl(url), p.appliedUrl, p.snapshot)); | ||||||
|               return map.call(preActivation.resolveData(), () => { |               return map.call(preActivation.resolveData(this.paramsInheritanceStrategy), () => { | ||||||
|                 this.triggerEvent( |                 this.triggerEvent( | ||||||
|                     new ResolveEnd(id, this.serializeUrl(url), p.appliedUrl, p.snapshot)); |                     new ResolveEnd(id, this.serializeUrl(url), p.appliedUrl, p.snapshot)); | ||||||
|                 return p; |                 return p; | ||||||
|  | |||||||
| @ -278,6 +278,16 @@ export interface ExtraOptions { | |||||||
|    * current URL. Default is 'ignore'. |    * current URL. Default is 'ignore'. | ||||||
|    */ |    */ | ||||||
|   onSameUrlNavigation?: 'reload'|'ignore'; |   onSameUrlNavigation?: 'reload'|'ignore'; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Defines how the router merges params, data and resolved data from parent to child | ||||||
|  |    * routes. Available options are: | ||||||
|  |    * | ||||||
|  |    * - `'emptyOnly'`, the default, only inherits parent params for path-less or component-less | ||||||
|  |    *   routes. | ||||||
|  |    * - `'always'`, enables unconditional inheritance of parent params. | ||||||
|  |    */ | ||||||
|  |   paramsInheritanceStrategy?: 'emptyOnly'|'always'; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function setupRouter( | export function setupRouter( | ||||||
| @ -314,6 +324,10 @@ export function setupRouter( | |||||||
|     router.onSameUrlNavigation = opts.onSameUrlNavigation; |     router.onSameUrlNavigation = opts.onSameUrlNavigation; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   if (opts.paramsInheritanceStrategy) { | ||||||
|  |     router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   return router; |   return router; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ import {shallowEqual, shallowEqualArrays} from './utils/collection'; | |||||||
| import {Tree, TreeNode} from './utils/tree'; | import {Tree, TreeNode} from './utils/tree'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * @whatItDoes Represents the state of the router. |  * @whatItDoes Represents the state of the router. | ||||||
|  * |  * | ||||||
| @ -174,6 +175,9 @@ export class ActivatedRoute { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** @internal */ | ||||||
|  | export type ParamsInheritanceStrategy = 'emptyOnly' | 'always'; | ||||||
|  | 
 | ||||||
| /** @internal */ | /** @internal */ | ||||||
| export type Inherited = { | export type Inherited = { | ||||||
|   params: Params, |   params: Params, | ||||||
| @ -181,29 +185,43 @@ export type Inherited = { | |||||||
|   resolve: Data, |   resolve: Data, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** @internal */ | /** | ||||||
| export function inheritedParamsDataResolve(route: ActivatedRouteSnapshot): Inherited { |  * Returns the inherited params, data, and resolve for a given route. | ||||||
|   const pathToRoot = route.pathFromRoot; |  * By default, this only inherits values up to the nearest path-less or component-less route. | ||||||
|  |  * @internal | ||||||
|  |  */ | ||||||
|  | export function inheritedParamsDataResolve( | ||||||
|  |     route: ActivatedRouteSnapshot, | ||||||
|  |     paramsInheritanceStrategy: ParamsInheritanceStrategy = 'emptyOnly'): Inherited { | ||||||
|  |   const pathFromRoot = route.pathFromRoot; | ||||||
| 
 | 
 | ||||||
|   let inhertingStartingFrom = pathToRoot.length - 1; |   let inheritingStartingFrom = 0; | ||||||
|  |   if (paramsInheritanceStrategy !== 'always') { | ||||||
|  |     inheritingStartingFrom = pathFromRoot.length - 1; | ||||||
| 
 | 
 | ||||||
|   while (inhertingStartingFrom >= 1) { |     while (inheritingStartingFrom >= 1) { | ||||||
|     const current = pathToRoot[inhertingStartingFrom]; |       const current = pathFromRoot[inheritingStartingFrom]; | ||||||
|     const parent = pathToRoot[inhertingStartingFrom - 1]; |       const parent = pathFromRoot[inheritingStartingFrom - 1]; | ||||||
|       // current route is an empty path => inherits its parent's params and data
 |       // current route is an empty path => inherits its parent's params and data
 | ||||||
|       if (current.routeConfig && current.routeConfig.path === '') { |       if (current.routeConfig && current.routeConfig.path === '') { | ||||||
|       inhertingStartingFrom--; |         inheritingStartingFrom--; | ||||||
| 
 | 
 | ||||||
|         // parent is componentless => current route should inherit its params and data
 |         // parent is componentless => current route should inherit its params and data
 | ||||||
|       } else if (!parent.component) { |       } else if (!parent.component) { | ||||||
|       inhertingStartingFrom--; |         inheritingStartingFrom--; | ||||||
| 
 | 
 | ||||||
|       } else { |       } else { | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   return pathToRoot.slice(inhertingStartingFrom).reduce((res, curr) => { |   return flattenInherited(pathFromRoot.slice(inheritingStartingFrom)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** @internal */ | ||||||
|  | function flattenInherited(pathFromRoot: ActivatedRouteSnapshot[]): Inherited { | ||||||
|  |   return pathFromRoot.reduce((res, curr) => { | ||||||
|     const params = {...res.params, ...curr.params}; |     const params = {...res.params, ...curr.params}; | ||||||
|     const data = {...res.data, ...curr.data}; |     const data = {...res.data, ...curr.data}; | ||||||
|     const resolve = {...res.resolve, ...curr._resolvedData}; |     const resolve = {...res.resolve, ...curr._resolvedData}; | ||||||
| @ -352,7 +370,7 @@ function setRouterState<U, T extends{_routerState: U}>(state: U, node: TreeNode< | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function serializeNode(node: TreeNode<ActivatedRouteSnapshot>): string { | function serializeNode(node: TreeNode<ActivatedRouteSnapshot>): string { | ||||||
|   const c = node.children.length > 0 ? ` { ${node.children.map(serializeNode).join(", ")} } ` : ''; |   const c = node.children.length > 0 ? ` { ${node.children.map(serializeNode).join(', ')} } ` : ''; | ||||||
|   return `${node.value}${c}`; |   return `${node.value}${c}`; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3794,6 +3794,19 @@ describe('Integration', () => { | |||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | describe('Testing router options', () => { | ||||||
|  |   describe('paramsInheritanceStrategy', () => { | ||||||
|  |     beforeEach(() => { | ||||||
|  |       TestBed.configureTestingModule( | ||||||
|  |           {imports: [RouterTestingModule.withRoutes([], {paramsInheritanceStrategy: 'always'})]}); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should configure the router', fakeAsync(inject([Router], (router: Router) => { | ||||||
|  |          expect(router.paramsInheritanceStrategy).toEqual('always'); | ||||||
|  |        }))); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| function expectEvents(events: Event[], pairs: any[]) { | function expectEvents(events: Event[], pairs: any[]) { | ||||||
|   expect(events.length).toEqual(pairs.length); |   expect(events.length).toEqual(pairs.length); | ||||||
|   for (let i = 0; i < events.length; ++i) { |   for (let i = 0; i < events.length; ++i) { | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ | |||||||
| 
 | 
 | ||||||
| import {Routes} from '../src/config'; | import {Routes} from '../src/config'; | ||||||
| import {recognize} from '../src/recognize'; | import {recognize} from '../src/recognize'; | ||||||
| import {ActivatedRouteSnapshot, RouterStateSnapshot} from '../src/router_state'; | import {ActivatedRouteSnapshot, ParamsInheritanceStrategy, RouterStateSnapshot, inheritedParamsDataResolve} from '../src/router_state'; | ||||||
| import {PRIMARY_OUTLET, Params} from '../src/shared'; | import {PRIMARY_OUTLET, Params} from '../src/shared'; | ||||||
| import {DefaultUrlSerializer, UrlTree} from '../src/url_tree'; | import {DefaultUrlSerializer, UrlTree} from '../src/url_tree'; | ||||||
| 
 | 
 | ||||||
| @ -201,7 +201,7 @@ describe('recognize', () => { | |||||||
|           }); |           }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should merge componentless route\'s data', () => { |     it('should inherit componentless route\'s data', () => { | ||||||
|       checkRecognize( |       checkRecognize( | ||||||
|           [{ |           [{ | ||||||
|             path: 'a', |             path: 'a', | ||||||
| @ -214,6 +214,34 @@ describe('recognize', () => { | |||||||
|           }); |           }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     it('should not inherit route\'s data if it has component', () => { | ||||||
|  |       checkRecognize( | ||||||
|  |           [{ | ||||||
|  |             path: 'a', | ||||||
|  |             component: ComponentA, | ||||||
|  |             data: {one: 1}, | ||||||
|  |             children: [{path: 'b', data: {two: 2}, component: ComponentB}] | ||||||
|  |           }], | ||||||
|  |           'a/b', (s: RouterStateSnapshot) => { | ||||||
|  |             const r: ActivatedRouteSnapshot = s.firstChild(<any>s.firstChild(s.root)) !; | ||||||
|  |             expect(r.data).toEqual({two: 2}); | ||||||
|  |           }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should inherit route\'s data if paramsInheritanceStrategy is \'always\'', () => { | ||||||
|  |       checkRecognize( | ||||||
|  |           [{ | ||||||
|  |             path: 'a', | ||||||
|  |             component: ComponentA, | ||||||
|  |             data: {one: 1}, | ||||||
|  |             children: [{path: 'b', data: {two: 2}, component: ComponentB}] | ||||||
|  |           }], | ||||||
|  |           'a/b', (s: RouterStateSnapshot) => { | ||||||
|  |             const r: ActivatedRouteSnapshot = s.firstChild(<any>s.firstChild(s.root)) !; | ||||||
|  |             expect(r.data).toEqual({one: 1, two: 2}); | ||||||
|  |           }, 'always'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     it('should set resolved data', () => { |     it('should set resolved data', () => { | ||||||
|       checkRecognize( |       checkRecognize( | ||||||
|           [{path: 'a', resolve: {one: 'some-token'}, component: ComponentA}], 'a', |           [{path: 'a', resolve: {one: 'some-token'}, component: ComponentA}], 'a', | ||||||
| @ -307,7 +335,7 @@ describe('recognize', () => { | |||||||
|             }); |             }); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       it('should match (non-termianl) when both primary and secondary and primary has a child', |       it('should match (non-terminal) when both primary and secondary and primary has a child', | ||||||
|          () => { |          () => { | ||||||
|            const config = [{ |            const config = [{ | ||||||
|              path: 'parent', |              path: 'parent', | ||||||
| @ -579,7 +607,7 @@ describe('recognize', () => { | |||||||
|           }); |           }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should merge params until encounters a normal route', () => { |     it('should inherit params until encounters a normal route', () => { | ||||||
|       checkRecognize( |       checkRecognize( | ||||||
|           [{ |           [{ | ||||||
|             path: 'p/:id', |             path: 'p/:id', | ||||||
| @ -606,6 +634,25 @@ describe('recognize', () => { | |||||||
|             checkActivatedRoute(c, 'c', {}, ComponentC); |             checkActivatedRoute(c, 'c', {}, ComponentC); | ||||||
|           }); |           }); | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     it('should inherit all params if paramsInheritanceStrategy is \'always\'', () => { | ||||||
|  |       checkRecognize( | ||||||
|  |           [{ | ||||||
|  |             path: 'p/:id', | ||||||
|  |             children: [{ | ||||||
|  |               path: 'a/:name', | ||||||
|  |               children: [{ | ||||||
|  |                 path: 'b', | ||||||
|  |                 component: ComponentB, | ||||||
|  |                 children: [{path: 'c', component: ComponentC}] | ||||||
|  |               }] | ||||||
|  |             }] | ||||||
|  |           }], | ||||||
|  |           'p/11/a/victor/b/c', (s: RouterStateSnapshot) => { | ||||||
|  |             const c = s.firstChild(s.firstChild(s.firstChild(s.firstChild(s.root) !) !) !) !; | ||||||
|  |             checkActivatedRoute(c, 'c', {id: '11', name: 'victor'}, ComponentC); | ||||||
|  |           }, 'always'); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   describe('empty URL leftovers', () => { |   describe('empty URL leftovers', () => { | ||||||
| @ -722,8 +769,11 @@ describe('recognize', () => { | |||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| function checkRecognize(config: Routes, url: string, callback: any): void { | function checkRecognize( | ||||||
|   recognize(RootComponent, config, tree(url), url).subscribe(callback, e => { throw e; }); |     config: Routes, url: string, callback: any, | ||||||
|  |     paramsInheritanceStrategy?: ParamsInheritanceStrategy): void { | ||||||
|  |   recognize(RootComponent, config, tree(url), url, paramsInheritanceStrategy) | ||||||
|  |       .subscribe(callback, e => { throw e; }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function checkActivatedRoute( | function checkActivatedRoute( | ||||||
|  | |||||||
| @ -498,7 +498,7 @@ function checkResolveData( | |||||||
|     future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void { |     future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void { | ||||||
|   const p = new PreActivation(future, curr, injector); |   const p = new PreActivation(future, curr, injector); | ||||||
|   p.initialize(new ChildrenOutletContexts()); |   p.initialize(new ChildrenOutletContexts()); | ||||||
|   p.resolveData().subscribe(check, (e) => { throw e; }); |   p.resolveData('emptyOnly').subscribe(check, (e) => { throw e; }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function checkGuards( | function checkGuards( | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ | |||||||
| import {Location, LocationStrategy} from '@angular/common'; | import {Location, LocationStrategy} from '@angular/common'; | ||||||
| import {MockLocationStrategy, SpyLocation} from '@angular/common/testing'; | import {MockLocationStrategy, SpyLocation} from '@angular/common/testing'; | ||||||
| import {Compiler, Injectable, Injector, ModuleWithProviders, NgModule, NgModuleFactory, NgModuleFactoryLoader, Optional} from '@angular/core'; | import {Compiler, Injectable, Injector, ModuleWithProviders, NgModule, NgModuleFactory, NgModuleFactoryLoader, Optional} from '@angular/core'; | ||||||
| import {ChildrenOutletContexts, NoPreloading, PreloadingStrategy, ROUTES, Route, Router, RouterModule, Routes, UrlHandlingStrategy, UrlSerializer, provideRoutes, ɵROUTER_PROVIDERS as ROUTER_PROVIDERS, ɵflatten as flatten} from '@angular/router'; | import {ChildrenOutletContexts, ExtraOptions, NoPreloading, PreloadingStrategy, ROUTER_CONFIGURATION, ROUTES, Route, Router, RouterModule, Routes, UrlHandlingStrategy, UrlSerializer, provideRoutes, ɵROUTER_PROVIDERS as ROUTER_PROVIDERS, ɵflatten as flatten} from '@angular/router'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -76,6 +76,13 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function isUrlHandlingStrategy(opts: ExtraOptions | UrlHandlingStrategy): | ||||||
|  |     opts is UrlHandlingStrategy { | ||||||
|  |   // This property check is needed because UrlHandlingStrategy is an interface and doesn't exist at
 | ||||||
|  |   // runtime.
 | ||||||
|  |   return 'shouldProcessUrl' in opts; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Router setup factory function used for testing. |  * Router setup factory function used for testing. | ||||||
|  * |  * | ||||||
| @ -84,9 +91,39 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader { | |||||||
| export function setupTestingRouter( | export function setupTestingRouter( | ||||||
|     urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location, |     urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location, | ||||||
|     loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], |     loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], | ||||||
|     urlHandlingStrategy?: UrlHandlingStrategy) { |     opts?: ExtraOptions, urlHandlingStrategy?: UrlHandlingStrategy): Router; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Router setup factory function used for testing. | ||||||
|  |  * | ||||||
|  |  * @deprecated As of v5.2. The 2nd-to-last argument should be `ExtraOptions`, not | ||||||
|  |  * `UrlHandlingStrategy` | ||||||
|  |  */ | ||||||
|  | export function setupTestingRouter( | ||||||
|  |     urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location, | ||||||
|  |     loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], | ||||||
|  |     urlHandlingStrategy?: UrlHandlingStrategy): Router; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Router setup factory function used for testing. | ||||||
|  |  * | ||||||
|  |  * @stable | ||||||
|  |  */ | ||||||
|  | export function setupTestingRouter( | ||||||
|  |     urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location, | ||||||
|  |     loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], | ||||||
|  |     opts?: ExtraOptions | UrlHandlingStrategy, urlHandlingStrategy?: UrlHandlingStrategy) { | ||||||
|   const router = new Router( |   const router = new Router( | ||||||
|       null !, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes)); |       null !, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes)); | ||||||
|  |   // Handle deprecated argument ordering.
 | ||||||
|  |   if (opts) { | ||||||
|  |     if (isUrlHandlingStrategy(opts)) { | ||||||
|  |       router.urlHandlingStrategy = opts; | ||||||
|  |     } else if (opts.paramsInheritanceStrategy) { | ||||||
|  |       router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   if (urlHandlingStrategy) { |   if (urlHandlingStrategy) { | ||||||
|     router.urlHandlingStrategy = urlHandlingStrategy; |     router.urlHandlingStrategy = urlHandlingStrategy; | ||||||
|   } |   } | ||||||
| @ -128,14 +165,20 @@ export function setupTestingRouter( | |||||||
|       useFactory: setupTestingRouter, |       useFactory: setupTestingRouter, | ||||||
|       deps: [ |       deps: [ | ||||||
|         UrlSerializer, ChildrenOutletContexts, Location, NgModuleFactoryLoader, Compiler, Injector, |         UrlSerializer, ChildrenOutletContexts, Location, NgModuleFactoryLoader, Compiler, Injector, | ||||||
|         ROUTES, [UrlHandlingStrategy, new Optional()] |         ROUTES, ROUTER_CONFIGURATION, [UrlHandlingStrategy, new Optional()] | ||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|     {provide: PreloadingStrategy, useExisting: NoPreloading}, provideRoutes([]) |     {provide: PreloadingStrategy, useExisting: NoPreloading}, provideRoutes([]) | ||||||
|   ] |   ] | ||||||
| }) | }) | ||||||
| export class RouterTestingModule { | export class RouterTestingModule { | ||||||
|   static withRoutes(routes: Routes): ModuleWithProviders { |   static withRoutes(routes: Routes, config?: ExtraOptions): ModuleWithProviders { | ||||||
|     return {ngModule: RouterTestingModule, providers: [provideRoutes(routes)]}; |     return { | ||||||
|  |       ngModule: RouterTestingModule, | ||||||
|  |       providers: [ | ||||||
|  |         provideRoutes(routes), | ||||||
|  |         {provide: ROUTER_CONFIGURATION, useValue: config ? config : {}}, | ||||||
|  |       ] | ||||||
|  |     }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								tools/public_api_guard/router/router.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								tools/public_api_guard/router/router.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -127,6 +127,7 @@ export interface ExtraOptions { | |||||||
|     errorHandler?: ErrorHandler; |     errorHandler?: ErrorHandler; | ||||||
|     initialNavigation?: InitialNavigation; |     initialNavigation?: InitialNavigation; | ||||||
|     onSameUrlNavigation?: 'reload' | 'ignore'; |     onSameUrlNavigation?: 'reload' | 'ignore'; | ||||||
|  |     paramsInheritanceStrategy?: 'emptyOnly' | 'always'; | ||||||
|     preloadingStrategy?: any; |     preloadingStrategy?: any; | ||||||
|     useHash?: boolean; |     useHash?: boolean; | ||||||
| } | } | ||||||
| @ -329,6 +330,7 @@ export declare class Router { | |||||||
|     readonly events: Observable<Event>; |     readonly events: Observable<Event>; | ||||||
|     navigated: boolean; |     navigated: boolean; | ||||||
|     onSameUrlNavigation: 'reload' | 'ignore'; |     onSameUrlNavigation: 'reload' | 'ignore'; | ||||||
|  |     paramsInheritanceStrategy: 'emptyOnly' | 'always'; | ||||||
|     routeReuseStrategy: RouteReuseStrategy; |     routeReuseStrategy: RouteReuseStrategy; | ||||||
|     readonly routerState: RouterState; |     readonly routerState: RouterState; | ||||||
|     readonly url: string; |     readonly url: string; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user