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…
Reference in New Issue