refactor(router): cleanup, simplification
This commit is contained in:
		
							parent
							
								
									6cb93c1fac
								
							
						
					
					
						commit
						81ca51a8f0
					
				| @ -37,8 +37,9 @@ import {PRIMARY_OUTLET} from '../shared'; | |||||||
|  */ |  */ | ||||||
| @Directive({selector: 'router-outlet'}) | @Directive({selector: 'router-outlet'}) | ||||||
| export class RouterOutlet implements OnDestroy { | export class RouterOutlet implements OnDestroy { | ||||||
|   private activated: ComponentRef<any>; |   private activated: ComponentRef<any>|null = null; | ||||||
|   private _activatedRoute: ActivatedRoute; |   private _activatedRoute: ActivatedRoute|null = null; | ||||||
|  |   private _outletName: string; | ||||||
|   public outletMap: RouterOutletMap; |   public outletMap: RouterOutletMap; | ||||||
| 
 | 
 | ||||||
|   @Output('activate') activateEvents = new EventEmitter<any>(); |   @Output('activate') activateEvents = new EventEmitter<any>(); | ||||||
| @ -46,11 +47,12 @@ export class RouterOutlet implements OnDestroy { | |||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|       private parentOutletMap: RouterOutletMap, private location: ViewContainerRef, |       private parentOutletMap: RouterOutletMap, private location: ViewContainerRef, | ||||||
|       private resolver: ComponentFactoryResolver, @Attribute('name') private name: string) { |       private resolver: ComponentFactoryResolver, @Attribute('name') name: string) { | ||||||
|     parentOutletMap.registerOutlet(name ? name : PRIMARY_OUTLET, this); |     this._outletName = name || PRIMARY_OUTLET; | ||||||
|  |     parentOutletMap.registerOutlet(this._outletName, this); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnDestroy(): void { this.parentOutletMap.removeOutlet(this.name ? this.name : PRIMARY_OUTLET); } |   ngOnDestroy(): void { this.parentOutletMap.removeOutlet(this._outletName); } | ||||||
| 
 | 
 | ||||||
|   /** @deprecated since v4 **/ |   /** @deprecated since v4 **/ | ||||||
|   get locationInjector(): Injector { return this.location.injector; } |   get locationInjector(): Injector { return this.location.injector; } | ||||||
| @ -58,24 +60,32 @@ export class RouterOutlet implements OnDestroy { | |||||||
|   get locationFactoryResolver(): ComponentFactoryResolver { return this.resolver; } |   get locationFactoryResolver(): ComponentFactoryResolver { return this.resolver; } | ||||||
| 
 | 
 | ||||||
|   get isActivated(): boolean { return !!this.activated; } |   get isActivated(): boolean { return !!this.activated; } | ||||||
|  | 
 | ||||||
|   get component(): Object { |   get component(): Object { | ||||||
|     if (!this.activated) throw new Error('Outlet is not activated'); |     if (!this.activated) throw new Error('Outlet is not activated'); | ||||||
|     return this.activated.instance; |     return this.activated.instance; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   get activatedRoute(): ActivatedRoute { |   get activatedRoute(): ActivatedRoute { | ||||||
|     if (!this.activated) throw new Error('Outlet is not activated'); |     if (!this.activated) throw new Error('Outlet is not activated'); | ||||||
|     return this._activatedRoute; |     return this._activatedRoute as ActivatedRoute; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * Called when the `RouteReuseStrategy` instructs to detach the subtree | ||||||
|  |    */ | ||||||
|   detach(): ComponentRef<any> { |   detach(): ComponentRef<any> { | ||||||
|     if (!this.activated) throw new Error('Outlet is not activated'); |     if (!this.activated) throw new Error('Outlet is not activated'); | ||||||
|     this.location.detach(); |     this.location.detach(); | ||||||
|     const r = this.activated; |     const cmp = this.activated; | ||||||
|     this.activated = null !; |     this.activated = null; | ||||||
|     this._activatedRoute = null !; |     this._activatedRoute = null; | ||||||
|     return r; |     return cmp; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree | ||||||
|  |    */ | ||||||
|   attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute) { |   attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute) { | ||||||
|     this.activated = ref; |     this.activated = ref; | ||||||
|     this._activatedRoute = activatedRoute; |     this._activatedRoute = activatedRoute; | ||||||
| @ -86,8 +96,8 @@ export class RouterOutlet implements OnDestroy { | |||||||
|     if (this.activated) { |     if (this.activated) { | ||||||
|       const c = this.component; |       const c = this.component; | ||||||
|       this.activated.destroy(); |       this.activated.destroy(); | ||||||
|       this.activated = null !; |       this.activated = null; | ||||||
|       this._activatedRoute = null !; |       this._activatedRoute = null; | ||||||
|       this.deactivateEvents.emit(c); |       this.deactivateEvents.emit(c); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @ -129,11 +139,11 @@ export class RouterOutlet implements OnDestroy { | |||||||
|     const component = <any>snapshot._routeConfig !.component; |     const component = <any>snapshot._routeConfig !.component; | ||||||
| 
 | 
 | ||||||
|     resolver = resolver || this.resolver; |     resolver = resolver || this.resolver; | ||||||
|     const factory = resolver.resolveComponentFactory(component) !; |     const factory = resolver.resolveComponentFactory(component); | ||||||
| 
 | 
 | ||||||
|     const injector = new OutletInjector(activatedRoute, outletMap, this.location.injector); |     const injector = new OutletInjector(activatedRoute, outletMap, this.location.injector); | ||||||
| 
 | 
 | ||||||
|     this.activated = this.location.createComponent(factory, this.location.length, injector, []); |     this.activated = this.location.createComponent(factory, this.location.length, injector); | ||||||
|     this.activated.changeDetectorRef.detectChanges(); |     this.activated.changeDetectorRef.detectChanges(); | ||||||
| 
 | 
 | ||||||
|     this.activateEvents.emit(this.activated.instance); |     this.activateEvents.emit(this.activated.instance); | ||||||
|  | |||||||
| @ -43,7 +43,7 @@ class Recognizer { | |||||||
| 
 | 
 | ||||||
|       const rootNode = new TreeNode<ActivatedRouteSnapshot>(root, children); |       const rootNode = new TreeNode<ActivatedRouteSnapshot>(root, children); | ||||||
|       const routeState = new RouterStateSnapshot(this.url, rootNode); |       const routeState = new RouterStateSnapshot(this.url, rootNode); | ||||||
|       this.inheriteParamsAndData(routeState._root); |       this.inheritParamsAndData(routeState._root); | ||||||
|       return of (routeState); |       return of (routeState); | ||||||
| 
 | 
 | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
| @ -52,14 +52,14 @@ class Recognizer { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   inheriteParamsAndData(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); | ||||||
|     route.params = Object.freeze(i.params); |     route.params = Object.freeze(i.params); | ||||||
|     route.data = Object.freeze(i.data); |     route.data = Object.freeze(i.data); | ||||||
| 
 | 
 | ||||||
|     routeNode.children.forEach(n => this.inheriteParamsAndData(n)); |     routeNode.children.forEach(n => this.inheritParamsAndData(n)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   processSegmentGroup(config: Route[], segmentGroup: UrlSegmentGroup, outlet: string): |   processSegmentGroup(config: Route[], segmentGroup: UrlSegmentGroup, outlet: string): | ||||||
|  | |||||||
| @ -48,3 +48,16 @@ export abstract class RouteReuseStrategy { | |||||||
|   /** Determines if a route should be reused */ |   /** Determines if a route should be reused */ | ||||||
|   abstract shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean; |   abstract shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Does not detach any subtrees. Reuses routes as long as their route config is the same. | ||||||
|  |  */ | ||||||
|  | export class DefaultRouteReuseStrategy implements RouteReuseStrategy { | ||||||
|  |   shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; } | ||||||
|  |   store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {} | ||||||
|  |   shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; } | ||||||
|  |   retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle|null { return null; } | ||||||
|  |   shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { | ||||||
|  |     return future.routeConfig === curr.routeConfig; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ import {createUrlTree} from './create_url_tree'; | |||||||
| import {RouterOutlet} from './directives/router_outlet'; | import {RouterOutlet} from './directives/router_outlet'; | ||||||
| import {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events'; | import {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events'; | ||||||
| import {recognize} from './recognize'; | import {recognize} from './recognize'; | ||||||
| import {DetachedRouteHandle, 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 {RouterOutletMap} from './router_outlet_map'; | import {RouterOutletMap} from './router_outlet_map'; | ||||||
| import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state'; | import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state'; | ||||||
| @ -192,19 +192,6 @@ function defaultRouterHook(snapshot: RouterStateSnapshot): Observable<void> { | |||||||
|   return of (null) as any; |   return of (null) as any; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Does not detach any subtrees. Reuses routes as long as their route config is the same. |  | ||||||
|  */ |  | ||||||
| export class DefaultRouteReuseStrategy implements RouteReuseStrategy { |  | ||||||
|   shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; } |  | ||||||
|   store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {} |  | ||||||
|   shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; } |  | ||||||
|   retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle|null { return null; } |  | ||||||
|   shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { |  | ||||||
|     return future.routeConfig === curr.routeConfig; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * @whatItDoes Provides the navigation and url manipulation capabilities. |  * @whatItDoes Provides the navigation and url manipulation capabilities. | ||||||
|  * |  * | ||||||
| @ -493,11 +480,11 @@ export class Router { | |||||||
|   isActive(url: string|UrlTree, exact: boolean): boolean { |   isActive(url: string|UrlTree, exact: boolean): boolean { | ||||||
|     if (url instanceof UrlTree) { |     if (url instanceof UrlTree) { | ||||||
|       return containsTree(this.currentUrlTree, url, exact); |       return containsTree(this.currentUrlTree, url, exact); | ||||||
|     } else { |     } | ||||||
|  | 
 | ||||||
|     const urlTree = this.urlSerializer.parse(url); |     const urlTree = this.urlSerializer.parse(url); | ||||||
|     return containsTree(this.currentUrlTree, urlTree, exact); |     return containsTree(this.currentUrlTree, urlTree, exact); | ||||||
|   } |   } | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   private removeEmptyProps(params: Params): Params { |   private removeEmptyProps(params: Params): Params { | ||||||
|     return Object.keys(params).reduce((result: Params, key: string) => { |     return Object.keys(params).reduce((result: Params, key: string) => { | ||||||
| @ -637,6 +624,7 @@ export class Router { | |||||||
| 
 | 
 | ||||||
|       // run preactivation: guards and data resolvers
 |       // run preactivation: guards and data resolvers
 | ||||||
|       let preActivation: PreActivation; |       let preActivation: PreActivation; | ||||||
|  | 
 | ||||||
|       const preactivationTraverse$ = map.call( |       const preactivationTraverse$ = map.call( | ||||||
|           beforePreactivationDone$, |           beforePreactivationDone$, | ||||||
|           ({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => { |           ({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => { | ||||||
| @ -771,10 +759,10 @@ class CanDeactivate { | |||||||
|   constructor(public component: Object|null, public route: ActivatedRouteSnapshot) {} |   constructor(public component: Object|null, public route: ActivatedRouteSnapshot) {} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| export class PreActivation { | export class PreActivation { | ||||||
|   private canActivateChecks: CanActivate[] = []; |   private canActivateChecks: CanActivate[] = []; | ||||||
|   private canDeactivateChecks: CanDeactivate[] = []; |   private canDeactivateChecks: CanDeactivate[] = []; | ||||||
|  | 
 | ||||||
|   constructor( |   constructor( | ||||||
|       private future: RouterStateSnapshot, private curr: RouterStateSnapshot, |       private future: RouterStateSnapshot, private curr: RouterStateSnapshot, | ||||||
|       private moduleInjector: Injector) {} |       private moduleInjector: Injector) {} | ||||||
| @ -808,13 +796,16 @@ export class PreActivation { | |||||||
|       outletMap: RouterOutletMap|null, futurePath: ActivatedRouteSnapshot[]): void { |       outletMap: RouterOutletMap|null, futurePath: ActivatedRouteSnapshot[]): void { | ||||||
|     const prevChildren = nodeChildrenAsMap(currNode); |     const prevChildren = nodeChildrenAsMap(currNode); | ||||||
| 
 | 
 | ||||||
|  |     // Process the children of the future route
 | ||||||
|     futureNode.children.forEach(c => { |     futureNode.children.forEach(c => { | ||||||
|       this.traverseRoutes(c, prevChildren[c.value.outlet], outletMap, futurePath.concat([c.value])); |       this.traverseRoutes(c, prevChildren[c.value.outlet], outletMap, futurePath.concat([c.value])); | ||||||
|       delete prevChildren[c.value.outlet]; |       delete prevChildren[c.value.outlet]; | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     // Process any children left from the current route (not active for the future route)
 | ||||||
|     forEach( |     forEach( | ||||||
|         prevChildren, (v: TreeNode<ActivatedRouteSnapshot>, k: string) => |         prevChildren, (v: TreeNode<ActivatedRouteSnapshot>, k: string) => | ||||||
|                           this.deactiveRouteAndItsChildren(v, outletMap !._outlets[k])); |                           this.deactivateRouteAndItsChildren(v, outletMap !._outlets[k])); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private traverseRoutes( |   private traverseRoutes( | ||||||
| @ -847,7 +838,7 @@ export class PreActivation { | |||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       if (curr) { |       if (curr) { | ||||||
|         this.deactiveRouteAndItsChildren(currNode, outlet); |         this.deactivateRouteAndItsChildren(currNode, outlet); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       this.canActivateChecks.push(new CanActivate(futurePath)); |       this.canActivateChecks.push(new CanActivate(futurePath)); | ||||||
| @ -879,18 +870,18 @@ export class PreActivation { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private deactiveRouteAndItsChildren( |   private deactivateRouteAndItsChildren( | ||||||
|       route: TreeNode<ActivatedRouteSnapshot>, outlet: RouterOutlet|null): void { |       route: TreeNode<ActivatedRouteSnapshot>, outlet: RouterOutlet|null): void { | ||||||
|     const prevChildren = nodeChildrenAsMap(route); |     const prevChildren = nodeChildrenAsMap(route); | ||||||
|     const r = route.value; |     const r = route.value; | ||||||
| 
 | 
 | ||||||
|     forEach(prevChildren, (v: TreeNode<ActivatedRouteSnapshot>, k: string) => { |     forEach(prevChildren, (v: TreeNode<ActivatedRouteSnapshot>, k: string) => { | ||||||
|       if (!r.component) { |       if (!r.component) { | ||||||
|         this.deactiveRouteAndItsChildren(v, outlet); |         this.deactivateRouteAndItsChildren(v, outlet); | ||||||
|       } else if (!!outlet) { |       } else if (outlet) { | ||||||
|         this.deactiveRouteAndItsChildren(v, outlet.outletMap._outlets[k]); |         this.deactivateRouteAndItsChildren(v, outlet.outletMap._outlets[k]); | ||||||
|       } else { |       } else { | ||||||
|         this.deactiveRouteAndItsChildren(v, null); |         this.deactivateRouteAndItsChildren(v, null); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
| @ -1020,74 +1011,112 @@ class ActivateRoutes { | |||||||
|     this.activateChildRoutes(futureRoot, currRoot, parentOutletMap); |     this.activateChildRoutes(futureRoot, currRoot, parentOutletMap); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // De-activate the child route that are not re-used for the future state
 | ||||||
|   private deactivateChildRoutes( |   private deactivateChildRoutes( | ||||||
|       futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>|null, |       futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>|null, | ||||||
|       outletMap: RouterOutletMap): void { |       outletMap: RouterOutletMap): void { | ||||||
|     const prevChildren: {[key: string]: any} = nodeChildrenAsMap(currNode); |     const prevChildren: {[outlet: string]: any} = nodeChildrenAsMap(currNode); | ||||||
|     futureNode.children.forEach(c => { | 
 | ||||||
|       this.deactivateRoutes(c, prevChildren[c.value.outlet], outletMap); |     // Recurse on the routes active in the future state to de-activate deeper children
 | ||||||
|       delete prevChildren[c.value.outlet]; |     futureNode.children.forEach(child => { | ||||||
|  |       const childOutletName = child.value.outlet; | ||||||
|  |       this.deactivateRoutes(child, prevChildren[childOutletName], outletMap); | ||||||
|  |       delete prevChildren[childOutletName]; | ||||||
|     }); |     }); | ||||||
|     forEach(prevChildren, (v: any, k: string) => this.deactiveRouteAndItsChildren(v, outletMap)); | 
 | ||||||
|  |     // De-activate the routes that will not be re-used
 | ||||||
|  |     forEach(prevChildren, (v: any, k: string) => this.deactivateRouteAndItsChildren(v, outletMap)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private deactivateRoutes( | ||||||
|  |       futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>, | ||||||
|  |       parentOutletMap: RouterOutletMap): void { | ||||||
|  |     const future = futureNode.value; | ||||||
|  |     const curr = currNode ? currNode.value : null; | ||||||
|  | 
 | ||||||
|  |     if (future === curr) { | ||||||
|  |       // Reusing the node, check to see of the children need to be de-activated
 | ||||||
|  |       if (future.component) { | ||||||
|  |         // If we have a normal route, we need to go through an outlet.
 | ||||||
|  |         const outlet = getOutlet(parentOutletMap, future); | ||||||
|  |         this.deactivateChildRoutes(futureNode, currNode, outlet.outletMap); | ||||||
|  |       } else { | ||||||
|  |         // if we have a componentless route, we recurse but keep the same outlet map.
 | ||||||
|  |         this.deactivateChildRoutes(futureNode, currNode, parentOutletMap); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       if (curr) { | ||||||
|  |         this.deactivateRouteAndItsChildren(currNode, parentOutletMap); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private deactivateRouteAndItsChildren( | ||||||
|  |       route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): void { | ||||||
|  |     if (this.routeReuseStrategy.shouldDetach(route.value.snapshot)) { | ||||||
|  |       this.detachAndStoreRouteSubtree(route, parentOutletMap); | ||||||
|  |     } else { | ||||||
|  |       this.deactivateRouteAndOutlet(route, parentOutletMap); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private detachAndStoreRouteSubtree( | ||||||
|  |       route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): void { | ||||||
|  |     const outlet = getOutlet(parentOutletMap, route.value); | ||||||
|  |     const componentRef = outlet.detach(); | ||||||
|  |     this.routeReuseStrategy.store(route.value.snapshot, {componentRef, route}); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private deactivateRouteAndOutlet( | ||||||
|  |       route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): void { | ||||||
|  |     const prevChildren: {[outletName: string]: any} = nodeChildrenAsMap(route); | ||||||
|  |     let outlet: RouterOutlet; | ||||||
|  | 
 | ||||||
|  |     // getOutlet throws when cannot find the right outlet,
 | ||||||
|  |     // which can happen if an outlet was in an NgIf and was removed
 | ||||||
|  |     try { | ||||||
|  |       outlet = getOutlet(parentOutletMap, route.value); | ||||||
|  |     } catch (e) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const outletMap = route.value.component ? outlet.outletMap : parentOutletMap; | ||||||
|  | 
 | ||||||
|  |     forEach( | ||||||
|  |         prevChildren, (v: any, k: string) => { this.deactivateRouteAndItsChildren(v, outletMap); }); | ||||||
|  | 
 | ||||||
|  |     outlet.deactivate(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private activateChildRoutes( |   private activateChildRoutes( | ||||||
|       futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>|null, |       futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>|null, | ||||||
|       outletMap: RouterOutletMap): void { |       outletMap: RouterOutletMap): void { | ||||||
|     const prevChildren: {[key: string]: any} = nodeChildrenAsMap(currNode); |     const prevChildren: {[outlet: string]: any} = nodeChildrenAsMap(currNode); | ||||||
|     futureNode.children.forEach( |     futureNode.children.forEach( | ||||||
|         c => { this.activateRoutes(c, prevChildren[c.value.outlet], outletMap); }); |         c => { this.activateRoutes(c, prevChildren[c.value.outlet], outletMap); }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   deactivateRoutes( |   private activateRoutes( | ||||||
|       futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>, |       futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>, | ||||||
|       parentOutletMap: RouterOutletMap): void { |       parentOutletMap: RouterOutletMap): void { | ||||||
|     const future = futureNode.value; |     const future = futureNode.value; | ||||||
|     const curr = currNode ? currNode.value : null; |     const curr = currNode ? currNode.value : null; | ||||||
| 
 | 
 | ||||||
|     // reusing the node
 |  | ||||||
|     if (future === curr) { |  | ||||||
|       // If we have a normal route, we need to go through an outlet.
 |  | ||||||
|       if (future.component) { |  | ||||||
|         const outlet = getOutlet(parentOutletMap, future); |  | ||||||
|         this.deactivateChildRoutes(futureNode, currNode, outlet.outletMap); |  | ||||||
| 
 |  | ||||||
|         // if we have a componentless route, we recurse but keep the same outlet map.
 |  | ||||||
|       } else { |  | ||||||
|         this.deactivateChildRoutes(futureNode, currNode, parentOutletMap); |  | ||||||
|       } |  | ||||||
|     } else { |  | ||||||
|       if (curr) { |  | ||||||
|         this.deactiveRouteAndItsChildren(currNode, parentOutletMap); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   activateRoutes( |  | ||||||
|       futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>, |  | ||||||
|       parentOutletMap: RouterOutletMap): void { |  | ||||||
|     const future = futureNode.value; |  | ||||||
|     const curr = currNode ? currNode.value : null; |  | ||||||
| 
 |  | ||||||
|     // reusing the node
 |  | ||||||
|     if (future === curr) { |  | ||||||
|       // advance the route to push the parameters
 |  | ||||||
|     advanceActivatedRoute(future); |     advanceActivatedRoute(future); | ||||||
| 
 | 
 | ||||||
|       // If we have a normal route, we need to go through an outlet.
 |     // reusing the node
 | ||||||
|  |     if (future === curr) { | ||||||
|       if (future.component) { |       if (future.component) { | ||||||
|  |         // If we have a normal route, we need to go through an outlet.
 | ||||||
|         const outlet = getOutlet(parentOutletMap, future); |         const outlet = getOutlet(parentOutletMap, future); | ||||||
|         this.activateChildRoutes(futureNode, currNode, outlet.outletMap); |         this.activateChildRoutes(futureNode, currNode, outlet.outletMap); | ||||||
| 
 |  | ||||||
|         // if we have a componentless route, we recurse but keep the same outlet map.
 |  | ||||||
|       } else { |       } else { | ||||||
|  |         // if we have a componentless route, we recurse but keep the same outlet map.
 | ||||||
|         this.activateChildRoutes(futureNode, currNode, parentOutletMap); |         this.activateChildRoutes(futureNode, currNode, parentOutletMap); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       // if we have a normal route, we need to advance the route
 |  | ||||||
|       // and place the component into the outlet. After that recurse.
 |  | ||||||
|       if (future.component) { |       if (future.component) { | ||||||
|         advanceActivatedRoute(future); |         // if we have a normal route, we need to place the component into the outlet and recurse.
 | ||||||
|         const outlet = getOutlet(parentOutletMap, futureNode.value); |         const outlet = getOutlet(parentOutletMap, futureNode.value); | ||||||
| 
 | 
 | ||||||
|         if (this.routeReuseStrategy.shouldAttach(future.snapshot)) { |         if (this.routeReuseStrategy.shouldAttach(future.snapshot)) { | ||||||
| @ -1101,10 +1130,8 @@ class ActivateRoutes { | |||||||
|           this.placeComponentIntoOutlet(outletMap, future, outlet); |           this.placeComponentIntoOutlet(outletMap, future, outlet); | ||||||
|           this.activateChildRoutes(futureNode, null, outletMap); |           this.activateChildRoutes(futureNode, null, outletMap); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         // if we have a componentless route, we recurse but keep the same outlet map.
 |  | ||||||
|       } else { |       } else { | ||||||
|         advanceActivatedRoute(future); |         // if we have a componentless route, we recurse but keep the same outlet map.
 | ||||||
|         this.activateChildRoutes(futureNode, null, parentOutletMap); |         this.activateChildRoutes(futureNode, null, parentOutletMap); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @ -1117,49 +1144,6 @@ class ActivateRoutes { | |||||||
| 
 | 
 | ||||||
|     outlet.activateWith(future, cmpFactoryResolver, outletMap); |     outlet.activateWith(future, cmpFactoryResolver, outletMap); | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   private deactiveRouteAndItsChildren( |  | ||||||
|       route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): void { |  | ||||||
|     if (this.routeReuseStrategy.shouldDetach(route.value.snapshot)) { |  | ||||||
|       this.detachAndStoreRouteSubtree(route, parentOutletMap); |  | ||||||
|     } else { |  | ||||||
|       this.deactiveRouteAndOutlet(route, parentOutletMap); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   private detachAndStoreRouteSubtree( |  | ||||||
|       route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): void { |  | ||||||
|     const outlet = getOutlet(parentOutletMap, route.value); |  | ||||||
|     const componentRef = outlet.detach(); |  | ||||||
|     this.routeReuseStrategy.store(route.value.snapshot, {componentRef, route}); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   private deactiveRouteAndOutlet(route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): |  | ||||||
|       void { |  | ||||||
|     const prevChildren: {[key: string]: any} = nodeChildrenAsMap(route); |  | ||||||
|     let outlet: RouterOutlet|null = null; |  | ||||||
| 
 |  | ||||||
|     // getOutlet throws when cannot find the right outlet,
 |  | ||||||
|     // which can happen if an outlet was in an NgIf and was removed
 |  | ||||||
|     try { |  | ||||||
|       outlet = getOutlet(parentOutletMap, route.value); |  | ||||||
|     } catch (e) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     const childOutletMap = outlet.outletMap; |  | ||||||
| 
 |  | ||||||
|     forEach(prevChildren, (v: any, k: string) => { |  | ||||||
|       if (route.value.component) { |  | ||||||
|         this.deactiveRouteAndItsChildren(v, childOutletMap); |  | ||||||
|       } else { |  | ||||||
|         this.deactiveRouteAndItsChildren(v, parentOutletMap); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     if (outlet && outlet.isActivated) { |  | ||||||
|       outlet.deactivate(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function advanceActivatedRouteNodeAndItsChildren(node: TreeNode<ActivatedRoute>): void { | function advanceActivatedRouteNodeAndItsChildren(node: TreeNode<ActivatedRoute>): void { | ||||||
| @ -1188,8 +1172,9 @@ function closestLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConf | |||||||
|   return null; |   return null; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Return the list of T indexed by outlet name
 | ||||||
| function nodeChildrenAsMap<T extends{outlet: string}>(node: TreeNode<T>| null) { | function nodeChildrenAsMap<T extends{outlet: string}>(node: TreeNode<T>| null) { | ||||||
|   const map: {[key: string]: TreeNode<T>} = {}; |   const map: {[outlet: string]: TreeNode<T>} = {}; | ||||||
| 
 | 
 | ||||||
|   if (node) { |   if (node) { | ||||||
|     node.children.forEach(child => map[child.value.outlet] = child); |     node.children.forEach(child => map[child.value.outlet] = child); | ||||||
|  | |||||||
| @ -25,5 +25,5 @@ export class RouterOutletMap { | |||||||
|   /** |   /** | ||||||
|    * Removes an outlet from this map. |    * Removes an outlet from this map. | ||||||
|    */ |    */ | ||||||
|   removeOutlet(name: string): void { this._outlets[name] = undefined !; } |   removeOutlet(name: string): void { this._outlets[name] = undefined as any; } | ||||||
| } | } | ||||||
|  | |||||||
| @ -51,7 +51,7 @@ export class RouterState extends Tree<ActivatedRoute> { | |||||||
|       /** The current snapshot of the router state */ |       /** The current snapshot of the router state */ | ||||||
|       public snapshot: RouterStateSnapshot) { |       public snapshot: RouterStateSnapshot) { | ||||||
|     super(root); |     super(root); | ||||||
|     setRouterStateSnapshot<RouterState, ActivatedRoute>(this, root); |     setRouterState(<RouterState>this, root); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   toString(): string { return this.snapshot.toString(); } |   toString(): string { return this.snapshot.toString(); } | ||||||
| @ -343,15 +343,15 @@ export class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> { | |||||||
|       /** The url from which this snapshot was created */ |       /** The url from which this snapshot was created */ | ||||||
|       public url: string, root: TreeNode<ActivatedRouteSnapshot>) { |       public url: string, root: TreeNode<ActivatedRouteSnapshot>) { | ||||||
|     super(root); |     super(root); | ||||||
|     setRouterStateSnapshot<RouterStateSnapshot, ActivatedRouteSnapshot>(this, root); |     setRouterState(<RouterStateSnapshot>this, root); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   toString(): string { return serializeNode(this._root); } |   toString(): string { return serializeNode(this._root); } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function setRouterStateSnapshot<U, T extends{_routerState: U}>(state: U, node: TreeNode<T>): void { | function setRouterState<U, T extends{_routerState: U}>(state: U, node: TreeNode<T>): void { | ||||||
|   node.value._routerState = state; |   node.value._routerState = state; | ||||||
|   node.children.forEach(c => setRouterStateSnapshot(state, c)); |   node.children.forEach(c => setRouterState(state, c)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function serializeNode(node: TreeNode<ActivatedRouteSnapshot>): string { | function serializeNode(node: TreeNode<ActivatedRouteSnapshot>): string { | ||||||
| @ -367,21 +367,22 @@ function serializeNode(node: TreeNode<ActivatedRouteSnapshot>): string { | |||||||
| export function advanceActivatedRoute(route: ActivatedRoute): void { | export function advanceActivatedRoute(route: ActivatedRoute): void { | ||||||
|   if (route.snapshot) { |   if (route.snapshot) { | ||||||
|     const currentSnapshot = route.snapshot; |     const currentSnapshot = route.snapshot; | ||||||
|     route.snapshot = route._futureSnapshot; |     const nextSnapshot = route._futureSnapshot; | ||||||
|     if (!shallowEqual(currentSnapshot.queryParams, route._futureSnapshot.queryParams)) { |     route.snapshot = nextSnapshot; | ||||||
|       (<any>route.queryParams).next(route._futureSnapshot.queryParams); |     if (!shallowEqual(currentSnapshot.queryParams, nextSnapshot.queryParams)) { | ||||||
|  |       (<any>route.queryParams).next(nextSnapshot.queryParams); | ||||||
|     } |     } | ||||||
|     if (currentSnapshot.fragment !== route._futureSnapshot.fragment) { |     if (currentSnapshot.fragment !== nextSnapshot.fragment) { | ||||||
|       (<any>route.fragment).next(route._futureSnapshot.fragment); |       (<any>route.fragment).next(nextSnapshot.fragment); | ||||||
|     } |     } | ||||||
|     if (!shallowEqual(currentSnapshot.params, route._futureSnapshot.params)) { |     if (!shallowEqual(currentSnapshot.params, nextSnapshot.params)) { | ||||||
|       (<any>route.params).next(route._futureSnapshot.params); |       (<any>route.params).next(nextSnapshot.params); | ||||||
|     } |     } | ||||||
|     if (!shallowEqualArrays(currentSnapshot.url, route._futureSnapshot.url)) { |     if (!shallowEqualArrays(currentSnapshot.url, nextSnapshot.url)) { | ||||||
|       (<any>route.url).next(route._futureSnapshot.url); |       (<any>route.url).next(nextSnapshot.url); | ||||||
|     } |     } | ||||||
|     if (!shallowEqual(currentSnapshot.data, route._futureSnapshot.data)) { |     if (!shallowEqual(currentSnapshot.data, nextSnapshot.data)) { | ||||||
|       (<any>route.data).next(route._futureSnapshot.data); |       (<any>route.data).next(nextSnapshot.data); | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
|     route.snapshot = route._futureSnapshot; |     route.snapshot = route._futureSnapshot; | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ export class Tree<T> { | |||||||
|    * @internal |    * @internal | ||||||
|    */ |    */ | ||||||
|   siblings(t: T): T[] { |   siblings(t: T): T[] { | ||||||
|     const p = findPath(t, this._root, []); |     const p = findPath(t, this._root); | ||||||
|     if (p.length < 2) return []; |     if (p.length < 2) return []; | ||||||
| 
 | 
 | ||||||
|     const c = p[p.length - 2].children.map(c => c.value); |     const c = p[p.length - 2].children.map(c => c.value); | ||||||
| @ -52,26 +52,32 @@ export class Tree<T> { | |||||||
|   /** |   /** | ||||||
|    * @internal |    * @internal | ||||||
|    */ |    */ | ||||||
|   pathFromRoot(t: T): T[] { return findPath(t, this._root, []).map(s => s.value); } |   pathFromRoot(t: T): T[] { return findPath(t, this._root).map(s => s.value); } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function findNode<T>(expected: T, c: TreeNode<T>): TreeNode<T>|null { | 
 | ||||||
|   if (expected === c.value) return c; | // DFS for the node matching the value
 | ||||||
|   for (const cc of c.children) { | function findNode<T>(value: T, node: TreeNode<T>): TreeNode<T>|null { | ||||||
|     const r = findNode(expected, cc); |   if (value === node.value) return node; | ||||||
|     if (r) return r; | 
 | ||||||
|  |   for (const child of node.children) { | ||||||
|  |     const node = findNode(value, child); | ||||||
|  |     if (node) return node; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   return null; |   return null; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function findPath<T>(expected: T, c: TreeNode<T>, collected: TreeNode<T>[]): TreeNode<T>[] { | // Return the path to the node with the given value using DFS
 | ||||||
|   collected.push(c); | function findPath<T>(value: T, node: TreeNode<T>): TreeNode<T>[] { | ||||||
|   if (expected === c.value) return collected; |   if (value === node.value) return [node]; | ||||||
| 
 | 
 | ||||||
|   for (const cc of c.children) { |   for (const child of node.children) { | ||||||
|     const cloned = collected.slice(0); |     const path = findPath(value, child); | ||||||
|     const r = findPath(expected, cc, cloned); |     if (path.length) { | ||||||
|     if (r.length > 0) return r; |       path.unshift(node); | ||||||
|  |       return path; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return []; |   return []; | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ | |||||||
| import {Routes} from '../src/config'; | import {Routes} from '../src/config'; | ||||||
| import {createRouterState} from '../src/create_router_state'; | import {createRouterState} from '../src/create_router_state'; | ||||||
| import {recognize} from '../src/recognize'; | import {recognize} from '../src/recognize'; | ||||||
| import {DefaultRouteReuseStrategy} from '../src/router'; | import {DefaultRouteReuseStrategy} from '../src/route_reuse_strategy'; | ||||||
| import {ActivatedRoute, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from '../src/router_state'; | import {ActivatedRoute, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from '../src/router_state'; | ||||||
| import {PRIMARY_OUTLET} from '../src/shared'; | import {PRIMARY_OUTLET} from '../src/shared'; | ||||||
| import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree} from '../src/url_tree'; | import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree} from '../src/url_tree'; | ||||||
| @ -80,7 +80,6 @@ describe('create router state', () => { | |||||||
|     const currP = state.firstChild(state.root) !; |     const currP = state.firstChild(state.root) !; | ||||||
|     expect(prevP).toBe(currP); |     expect(prevP).toBe(currP); | ||||||
| 
 | 
 | ||||||
|     const prevC = prevState.children(prevP); |  | ||||||
|     const currC = state.children(currP); |     const currC = state.children(currP); | ||||||
| 
 | 
 | ||||||
|     expect(currP._futureSnapshot.params).toEqual({id: '2', p: '22'}); |     expect(currP._futureSnapshot.params).toEqual({id: '2', p: '22'}); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user