revert: refactor(router): don't run the change detection every time an outlet is activated
This reverts commit 198edb3109
.
This commit is contained in:
parent
6531806996
commit
a0a6029915
|
@ -6,8 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Attribute, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, OnDestroy, OnInit, Output, ViewContainerRef} from '@angular/core';
|
import {Attribute, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, OnDestroy, Output, ReflectiveInjector, ResolvedReflectiveProvider, ViewContainerRef} from '@angular/core';
|
||||||
import {ChildrenOutletContexts} from '../router_outlet_context';
|
import {RouterOutletMap} from '../router_outlet_map';
|
||||||
import {ActivatedRoute} from '../router_state';
|
import {ActivatedRoute} from '../router_state';
|
||||||
import {PRIMARY_OUTLET} from '../shared';
|
import {PRIMARY_OUTLET} from '../shared';
|
||||||
|
|
||||||
|
@ -36,39 +36,23 @@ import {PRIMARY_OUTLET} from '../shared';
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
@Directive({selector: 'router-outlet'})
|
@Directive({selector: 'router-outlet'})
|
||||||
export class RouterOutlet implements OnDestroy, OnInit {
|
export class RouterOutlet implements OnDestroy {
|
||||||
private activated: ComponentRef<any>|null = null;
|
private activated: ComponentRef<any>|null = null;
|
||||||
private _activatedRoute: ActivatedRoute|null = null;
|
private _activatedRoute: ActivatedRoute|null = null;
|
||||||
private name: string;
|
private _outletName: string;
|
||||||
|
public outletMap: RouterOutletMap;
|
||||||
|
|
||||||
@Output('activate') activateEvents = new EventEmitter<any>();
|
@Output('activate') activateEvents = new EventEmitter<any>();
|
||||||
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
|
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef,
|
private parentOutletMap: RouterOutletMap, private location: ViewContainerRef,
|
||||||
private resolver: ComponentFactoryResolver, @Attribute('name') name: string) {
|
private resolver: ComponentFactoryResolver, @Attribute('name') name: string) {
|
||||||
this.name = name || PRIMARY_OUTLET;
|
this._outletName = name || PRIMARY_OUTLET;
|
||||||
parentContexts.onChildOutletCreated(this.name, this);
|
parentOutletMap.registerOutlet(this._outletName, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void { this.parentContexts.onChildOutletDestroyed(this.name); }
|
ngOnDestroy(): void { this.parentOutletMap.removeOutlet(this._outletName); }
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
if (!this.activated) {
|
|
||||||
// If the outlet was not instantiated at the time the route got activated we need to populate
|
|
||||||
// the outlet when it is initialized.
|
|
||||||
const context = this.parentContexts.getContext(this.name);
|
|
||||||
if (context && context.route) {
|
|
||||||
if (context.attachRef) {
|
|
||||||
// `attachRef` is populated when there is an existing component to mount
|
|
||||||
this.attach(context.attachRef, context.route);
|
|
||||||
} else {
|
|
||||||
// otherwise the component defined in the configuration is created
|
|
||||||
this.activateWith(context.route, context.resolver || null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @deprecated since v4 **/
|
/** @deprecated since v4 **/
|
||||||
get locationInjector(): Injector { return this.location.injector; }
|
get locationInjector(): Injector { return this.location.injector; }
|
||||||
|
@ -118,34 +102,65 @@ export class RouterOutlet implements OnDestroy, OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
|
/** @deprecated since v4, use {@link #activateWith} */
|
||||||
|
activate(
|
||||||
|
activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver, injector: Injector,
|
||||||
|
providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void {
|
||||||
if (this.isActivated) {
|
if (this.isActivated) {
|
||||||
throw new Error('Cannot activate an already activated outlet');
|
throw new Error('Cannot activate an already activated outlet');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.outletMap = outletMap;
|
||||||
this._activatedRoute = activatedRoute;
|
this._activatedRoute = activatedRoute;
|
||||||
|
|
||||||
|
const snapshot = activatedRoute._futureSnapshot;
|
||||||
|
const component: any = <any>snapshot._routeConfig !.component;
|
||||||
|
const factory = resolver.resolveComponentFactory(component) !;
|
||||||
|
|
||||||
|
const inj = ReflectiveInjector.fromResolvedProviders(providers, injector);
|
||||||
|
|
||||||
|
this.activated = this.location.createComponent(factory, this.location.length, inj, []);
|
||||||
|
this.activated.changeDetectorRef.detectChanges();
|
||||||
|
|
||||||
|
this.activateEvents.emit(this.activated.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
activateWith(
|
||||||
|
activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null,
|
||||||
|
outletMap: RouterOutletMap) {
|
||||||
|
if (this.isActivated) {
|
||||||
|
throw new Error('Cannot activate an already activated outlet');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outletMap = outletMap;
|
||||||
|
this._activatedRoute = activatedRoute;
|
||||||
|
|
||||||
const snapshot = activatedRoute._futureSnapshot;
|
const snapshot = activatedRoute._futureSnapshot;
|
||||||
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 childContexts = this.parentContexts.getOrCreateContext(this.name).children;
|
|
||||||
const injector = new OutletInjector(activatedRoute, childContexts, 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.activateEvents.emit(this.activated.instance);
|
this.activateEvents.emit(this.activated.instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OutletInjector implements Injector {
|
class OutletInjector implements Injector {
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute, private childContexts: ChildrenOutletContexts,
|
private route: ActivatedRoute, private map: RouterOutletMap, private parent: Injector) {}
|
||||||
private parent: Injector) {}
|
|
||||||
|
|
||||||
get(token: any, notFoundValue?: any): any {
|
get(token: any, notFoundValue?: any): any {
|
||||||
if (token === ActivatedRoute) {
|
if (token === ActivatedRoute) {
|
||||||
return this.route;
|
return this.route;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token === ChildrenOutletContexts) {
|
if (token === RouterOutletMap) {
|
||||||
return this.childContexts;
|
return this.map;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.parent.get(token, notFoundValue);
|
return this.parent.get(token, notFoundValue);
|
||||||
|
|
|
@ -17,7 +17,7 @@ export {DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
|
||||||
export {NavigationExtras, Router} from './router';
|
export {NavigationExtras, Router} from './router';
|
||||||
export {ROUTES} from './router_config_loader';
|
export {ROUTES} from './router_config_loader';
|
||||||
export {ExtraOptions, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, RouterModule, provideRoutes} from './router_module';
|
export {ExtraOptions, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, RouterModule, provideRoutes} from './router_module';
|
||||||
export {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
|
export {RouterOutletMap} from './router_outlet_map';
|
||||||
export {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
|
export {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
|
||||||
export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './router_state';
|
export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './router_state';
|
||||||
export {PRIMARY_OUTLET, ParamMap, Params, convertToParamMap} from './shared';
|
export {PRIMARY_OUTLET, ParamMap, Params, convertToParamMap} from './shared';
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
import {ComponentRef} from '@angular/core';
|
import {ComponentRef} from '@angular/core';
|
||||||
|
|
||||||
import {OutletContext} from './router_outlet_context';
|
|
||||||
import {ActivatedRoute, ActivatedRouteSnapshot} from './router_state';
|
import {ActivatedRoute, ActivatedRouteSnapshot} from './router_state';
|
||||||
import {TreeNode} from './utils/tree';
|
import {TreeNode} from './utils/tree';
|
||||||
|
|
||||||
|
@ -24,7 +23,6 @@ export type DetachedRouteHandle = {};
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export type DetachedRouteHandleInternal = {
|
export type DetachedRouteHandleInternal = {
|
||||||
contexts: Map<string, OutletContext>,
|
|
||||||
componentRef: ComponentRef<any>,
|
componentRef: ComponentRef<any>,
|
||||||
route: TreeNode<ActivatedRoute>,
|
route: TreeNode<ActivatedRoute>,
|
||||||
};
|
};
|
||||||
|
@ -38,11 +36,7 @@ export abstract class RouteReuseStrategy {
|
||||||
/** Determines if this route (and its subtree) should be detached to be reused later */
|
/** Determines if this route (and its subtree) should be detached to be reused later */
|
||||||
abstract shouldDetach(route: ActivatedRouteSnapshot): boolean;
|
abstract shouldDetach(route: ActivatedRouteSnapshot): boolean;
|
||||||
|
|
||||||
/**
|
/** Stores the detached route */
|
||||||
* Stores the detached route.
|
|
||||||
*
|
|
||||||
* Storing a `null` value should erase the previously stored value.
|
|
||||||
*/
|
|
||||||
abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle|null): void;
|
abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle|null): void;
|
||||||
|
|
||||||
/** Determines if this route (and its subtree) should be reattached */
|
/** Determines if this route (and its subtree) should be reattached */
|
||||||
|
|
|
@ -25,13 +25,14 @@ import {applyRedirects} from './apply_redirects';
|
||||||
import {LoadedRouterConfig, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, validateConfig} from './config';
|
import {LoadedRouterConfig, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, validateConfig} from './config';
|
||||||
import {createRouterState} from './create_router_state';
|
import {createRouterState} from './create_router_state';
|
||||||
import {createUrlTree} from './create_url_tree';
|
import {createUrlTree} from './create_url_tree';
|
||||||
|
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 {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, OutletContext} from './router_outlet_context';
|
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';
|
||||||
import {Params, isNavigationCancelingError} from './shared';
|
import {PRIMARY_OUTLET, 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';
|
||||||
import {andObservables, forEach, shallowEqual, waitForMap, wrapIntoObservable} from './utils/collection';
|
import {andObservables, forEach, shallowEqual, waitForMap, wrapIntoObservable} from './utils/collection';
|
||||||
|
@ -250,7 +251,7 @@ export class Router {
|
||||||
// TODO: vsavkin make internal after the final is out.
|
// TODO: vsavkin make internal after the final is out.
|
||||||
constructor(
|
constructor(
|
||||||
private rootComponentType: Type<any>|null, private urlSerializer: UrlSerializer,
|
private rootComponentType: Type<any>|null, private urlSerializer: UrlSerializer,
|
||||||
private rootContexts: ChildrenOutletContexts, private location: Location, injector: Injector,
|
private outletMap: RouterOutletMap, private location: Location, injector: Injector,
|
||||||
loader: NgModuleFactoryLoader, compiler: Compiler, public config: Routes) {
|
loader: NgModuleFactoryLoader, compiler: Compiler, public config: Routes) {
|
||||||
const onLoadStart = (r: Route) => this.triggerEvent(new RouteConfigLoadStart(r));
|
const onLoadStart = (r: Route) => this.triggerEvent(new RouteConfigLoadStart(r));
|
||||||
const onLoadEnd = (r: Route) => this.triggerEvent(new RouteConfigLoadEnd(r));
|
const onLoadEnd = (r: Route) => this.triggerEvent(new RouteConfigLoadEnd(r));
|
||||||
|
@ -630,7 +631,7 @@ export class Router {
|
||||||
const moduleInjector = this.ngModule.injector;
|
const moduleInjector = this.ngModule.injector;
|
||||||
preActivation =
|
preActivation =
|
||||||
new PreActivation(snapshot, this.currentRouterState.snapshot, moduleInjector);
|
new PreActivation(snapshot, this.currentRouterState.snapshot, moduleInjector);
|
||||||
preActivation.traverse(this.rootContexts);
|
preActivation.traverse(this.outletMap);
|
||||||
return {appliedUrl, snapshot};
|
return {appliedUrl, snapshot};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -701,7 +702,7 @@ export class Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
new ActivateRoutes(this.routeReuseStrategy, state, storedState)
|
new ActivateRoutes(this.routeReuseStrategy, state, storedState)
|
||||||
.activate(this.rootContexts);
|
.activate(this.outletMap);
|
||||||
|
|
||||||
navigationIsSuccessful = true;
|
navigationIsSuccessful = true;
|
||||||
})
|
})
|
||||||
|
@ -766,10 +767,10 @@ export class PreActivation {
|
||||||
private future: RouterStateSnapshot, private curr: RouterStateSnapshot,
|
private future: RouterStateSnapshot, private curr: RouterStateSnapshot,
|
||||||
private moduleInjector: Injector) {}
|
private moduleInjector: Injector) {}
|
||||||
|
|
||||||
traverse(parentContexts: ChildrenOutletContexts): void {
|
traverse(parentOutletMap: RouterOutletMap): void {
|
||||||
const futureRoot = this.future._root;
|
const futureRoot = this.future._root;
|
||||||
const currRoot = this.curr ? this.curr._root : null;
|
const currRoot = this.curr ? this.curr._root : null;
|
||||||
this.traverseChildRoutes(futureRoot, currRoot, parentContexts, [futureRoot.value]);
|
this.traverseChildRoutes(futureRoot, currRoot, parentOutletMap, [futureRoot.value]);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkGuards(): Observable<boolean> {
|
checkGuards(): Observable<boolean> {
|
||||||
|
@ -792,35 +793,34 @@ export class PreActivation {
|
||||||
|
|
||||||
private traverseChildRoutes(
|
private traverseChildRoutes(
|
||||||
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>|null,
|
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>|null,
|
||||||
contexts: ChildrenOutletContexts|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
|
// Process the children of the future route
|
||||||
futureNode.children.forEach(c => {
|
futureNode.children.forEach(c => {
|
||||||
this.traverseRoutes(c, prevChildren[c.value.outlet], contexts, 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)
|
// 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.deactivateRouteAndItsChildren(v, contexts !.getContext(k)));
|
this.deactivateRouteAndItsChildren(v, outletMap !._outlets[k]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private traverseRoutes(
|
private traverseRoutes(
|
||||||
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>,
|
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>,
|
||||||
parentContexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
|
parentOutletMap: RouterOutletMap|null, futurePath: ActivatedRouteSnapshot[]): void {
|
||||||
const future = futureNode.value;
|
const future = futureNode.value;
|
||||||
const curr = currNode ? currNode.value : null;
|
const curr = currNode ? currNode.value : null;
|
||||||
const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;
|
const outlet = parentOutletMap ? parentOutletMap._outlets[futureNode.value.outlet] : null;
|
||||||
|
|
||||||
// reusing the node
|
// reusing the node
|
||||||
if (curr && future._routeConfig === curr._routeConfig) {
|
if (curr && future._routeConfig === curr._routeConfig) {
|
||||||
if (this.shouldRunGuardsAndResolvers(
|
if (this.shouldRunGuardsAndResolvers(
|
||||||
curr, future, future._routeConfig !.runGuardsAndResolvers)) {
|
curr, future, future._routeConfig !.runGuardsAndResolvers)) {
|
||||||
this.canActivateChecks.push(new CanActivate(futurePath));
|
this.canActivateChecks.push(new CanActivate(futurePath));
|
||||||
const outlet = context !.outlet !;
|
this.canDeactivateChecks.push(new CanDeactivate(outlet !.component, curr));
|
||||||
this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
|
|
||||||
} else {
|
} else {
|
||||||
// we need to set the data
|
// we need to set the data
|
||||||
future.data = curr.data;
|
future.data = curr.data;
|
||||||
|
@ -830,25 +830,25 @@ export class PreActivation {
|
||||||
// If we have a component, we need to go through an outlet.
|
// If we have a component, we need to go through an outlet.
|
||||||
if (future.component) {
|
if (future.component) {
|
||||||
this.traverseChildRoutes(
|
this.traverseChildRoutes(
|
||||||
futureNode, currNode, context ? context.children : null, futurePath);
|
futureNode, currNode, outlet ? outlet.outletMap : null, futurePath);
|
||||||
|
|
||||||
// if we have a componentless route, we recurse but keep the same outlet map.
|
// if we have a componentless route, we recurse but keep the same outlet map.
|
||||||
} else {
|
} else {
|
||||||
this.traverseChildRoutes(futureNode, currNode, parentContexts, futurePath);
|
this.traverseChildRoutes(futureNode, currNode, parentOutletMap, futurePath);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (curr) {
|
if (curr) {
|
||||||
this.deactivateRouteAndItsChildren(currNode, context);
|
this.deactivateRouteAndItsChildren(currNode, outlet);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canActivateChecks.push(new CanActivate(futurePath));
|
this.canActivateChecks.push(new CanActivate(futurePath));
|
||||||
// If we have a component, we need to go through an outlet.
|
// If we have a component, we need to go through an outlet.
|
||||||
if (future.component) {
|
if (future.component) {
|
||||||
this.traverseChildRoutes(futureNode, null, context ? context.children : null, futurePath);
|
this.traverseChildRoutes(futureNode, null, outlet ? outlet.outletMap : null, futurePath);
|
||||||
|
|
||||||
// if we have a componentless route, we recurse but keep the same outlet map.
|
// if we have a componentless route, we recurse but keep the same outlet map.
|
||||||
} else {
|
} else {
|
||||||
this.traverseChildRoutes(futureNode, null, parentContexts, futurePath);
|
this.traverseChildRoutes(futureNode, null, parentOutletMap, futurePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -871,24 +871,24 @@ export class PreActivation {
|
||||||
}
|
}
|
||||||
|
|
||||||
private deactivateRouteAndItsChildren(
|
private deactivateRouteAndItsChildren(
|
||||||
route: TreeNode<ActivatedRouteSnapshot>, context: OutletContext|null): void {
|
route: TreeNode<ActivatedRouteSnapshot>, outlet: RouterOutlet|null): void {
|
||||||
const children = nodeChildrenAsMap(route);
|
const prevChildren = nodeChildrenAsMap(route);
|
||||||
const r = route.value;
|
const r = route.value;
|
||||||
|
|
||||||
forEach(children, (node: TreeNode<ActivatedRouteSnapshot>, childName: string) => {
|
forEach(prevChildren, (v: TreeNode<ActivatedRouteSnapshot>, k: string) => {
|
||||||
if (!r.component) {
|
if (!r.component) {
|
||||||
this.deactivateRouteAndItsChildren(node, context);
|
this.deactivateRouteAndItsChildren(v, outlet);
|
||||||
} else if (context) {
|
} else if (outlet) {
|
||||||
this.deactivateRouteAndItsChildren(node, context.children.getContext(childName));
|
this.deactivateRouteAndItsChildren(v, outlet.outletMap._outlets[k]);
|
||||||
} else {
|
} else {
|
||||||
this.deactivateRouteAndItsChildren(node, null);
|
this.deactivateRouteAndItsChildren(v, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!r.component) {
|
if (!r.component) {
|
||||||
this.canDeactivateChecks.push(new CanDeactivate(null, r));
|
this.canDeactivateChecks.push(new CanDeactivate(null, r));
|
||||||
} else if (context && context.outlet && context.outlet.isActivated) {
|
} else if (outlet && outlet.isActivated) {
|
||||||
this.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, r));
|
this.canDeactivateChecks.push(new CanDeactivate(outlet.component, r));
|
||||||
} else {
|
} else {
|
||||||
this.canDeactivateChecks.push(new CanDeactivate(null, r));
|
this.canDeactivateChecks.push(new CanDeactivate(null, r));
|
||||||
}
|
}
|
||||||
|
@ -1002,109 +1002,103 @@ class ActivateRoutes {
|
||||||
private routeReuseStrategy: RouteReuseStrategy, private futureState: RouterState,
|
private routeReuseStrategy: RouteReuseStrategy, private futureState: RouterState,
|
||||||
private currState: RouterState) {}
|
private currState: RouterState) {}
|
||||||
|
|
||||||
activate(parentContexts: ChildrenOutletContexts): void {
|
activate(parentOutletMap: RouterOutletMap): void {
|
||||||
const futureRoot = this.futureState._root;
|
const futureRoot = this.futureState._root;
|
||||||
const currRoot = this.currState ? this.currState._root : null;
|
const currRoot = this.currState ? this.currState._root : null;
|
||||||
|
|
||||||
this.deactivateChildRoutes(futureRoot, currRoot, parentContexts);
|
this.deactivateChildRoutes(futureRoot, currRoot, parentOutletMap);
|
||||||
advanceActivatedRoute(this.futureState.root);
|
advanceActivatedRoute(this.futureState.root);
|
||||||
this.activateChildRoutes(futureRoot, currRoot, parentContexts);
|
this.activateChildRoutes(futureRoot, currRoot, parentOutletMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// De-activate the child route that are not re-used for the future state
|
// 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,
|
||||||
contexts: ChildrenOutletContexts): void {
|
outletMap: RouterOutletMap): void {
|
||||||
const children: {[outletName: string]: TreeNode<ActivatedRoute>} = nodeChildrenAsMap(currNode);
|
const prevChildren: {[outlet: string]: any} = nodeChildrenAsMap(currNode);
|
||||||
|
|
||||||
// Recurse on the routes active in the future state to de-activate deeper children
|
// Recurse on the routes active in the future state to de-activate deeper children
|
||||||
futureNode.children.forEach(futureChild => {
|
futureNode.children.forEach(child => {
|
||||||
const childOutletName = futureChild.value.outlet;
|
const childOutletName = child.value.outlet;
|
||||||
this.deactivateRoutes(futureChild, children[childOutletName], contexts);
|
this.deactivateRoutes(child, prevChildren[childOutletName], outletMap);
|
||||||
delete children[childOutletName];
|
delete prevChildren[childOutletName];
|
||||||
});
|
});
|
||||||
|
|
||||||
// De-activate the routes that will not be re-used
|
// De-activate the routes that will not be re-used
|
||||||
forEach(children, (v: TreeNode<ActivatedRoute>, childName: string) => {
|
forEach(prevChildren, (v: any, k: string) => this.deactivateRouteAndItsChildren(v, outletMap));
|
||||||
this.deactivateRouteAndItsChildren(v, contexts);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private deactivateRoutes(
|
private deactivateRoutes(
|
||||||
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>,
|
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>,
|
||||||
parentContext: ChildrenOutletContexts): void {
|
parentOutletMap: RouterOutletMap): void {
|
||||||
const future = futureNode.value;
|
const future = futureNode.value;
|
||||||
const curr = currNode ? currNode.value : null;
|
const curr = currNode ? currNode.value : null;
|
||||||
|
|
||||||
if (future === curr) {
|
if (future === curr) {
|
||||||
// Reusing the node, check to see if the children need to be de-activated
|
// Reusing the node, check to see of the children need to be de-activated
|
||||||
if (future.component) {
|
if (future.component) {
|
||||||
// If we have a normal route, we need to go through an outlet.
|
// If we have a normal route, we need to go through an outlet.
|
||||||
const context = parentContext.getContext(future.outlet);
|
const outlet = getOutlet(parentOutletMap, future);
|
||||||
if (context) {
|
this.deactivateChildRoutes(futureNode, currNode, outlet.outletMap);
|
||||||
this.deactivateChildRoutes(futureNode, currNode, context.children);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// if we have a componentless route, we recurse but keep the same outlet map.
|
// if we have a componentless route, we recurse but keep the same outlet map.
|
||||||
this.deactivateChildRoutes(futureNode, currNode, parentContext);
|
this.deactivateChildRoutes(futureNode, currNode, parentOutletMap);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (curr) {
|
if (curr) {
|
||||||
// Deactivate the current route which will not be re-used
|
this.deactivateRouteAndItsChildren(currNode, parentOutletMap);
|
||||||
this.deactivateRouteAndItsChildren(currNode, parentContext);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private deactivateRouteAndItsChildren(
|
private deactivateRouteAndItsChildren(
|
||||||
route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts): void {
|
route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): void {
|
||||||
if (this.routeReuseStrategy.shouldDetach(route.value.snapshot)) {
|
if (this.routeReuseStrategy.shouldDetach(route.value.snapshot)) {
|
||||||
this.detachAndStoreRouteSubtree(route, parentContexts);
|
this.detachAndStoreRouteSubtree(route, parentOutletMap);
|
||||||
} else {
|
} else {
|
||||||
this.deactivateRouteAndOutlet(route, parentContexts);
|
this.deactivateRouteAndOutlet(route, parentOutletMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private detachAndStoreRouteSubtree(
|
private detachAndStoreRouteSubtree(
|
||||||
route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts): void {
|
route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): void {
|
||||||
const context = parentContexts.getContext(route.value.outlet);
|
const outlet = getOutlet(parentOutletMap, route.value);
|
||||||
if (context && context.outlet) {
|
const componentRef = outlet.detach();
|
||||||
const componentRef = context.outlet.detach();
|
this.routeReuseStrategy.store(route.value.snapshot, {componentRef, route});
|
||||||
const contexts = context.children.onOutletDeactivated();
|
|
||||||
this.routeReuseStrategy.store(route.value.snapshot, {componentRef, route, contexts});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private deactivateRouteAndOutlet(
|
private deactivateRouteAndOutlet(
|
||||||
route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts): void {
|
route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): void {
|
||||||
const context = parentContexts.getContext(route.value.outlet);
|
const prevChildren: {[outletName: string]: any} = nodeChildrenAsMap(route);
|
||||||
|
let outlet: RouterOutlet;
|
||||||
|
|
||||||
if (context) {
|
// getOutlet throws when cannot find the right outlet,
|
||||||
const children: {[outletName: string]: any} = nodeChildrenAsMap(route);
|
// which can happen if an outlet was in an NgIf and was removed
|
||||||
const contexts = route.value.component ? context.children : parentContexts;
|
try {
|
||||||
|
outlet = getOutlet(parentOutletMap, route.value);
|
||||||
forEach(children, (v: any, k: string) => {this.deactivateRouteAndItsChildren(v, contexts)});
|
} catch (e) {
|
||||||
|
return;
|
||||||
if (context.outlet) {
|
|
||||||
// Destroy the component
|
|
||||||
context.outlet.deactivate();
|
|
||||||
// Destroy the contexts for all the outlets that were in the component
|
|
||||||
context.children.onOutletDeactivated();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
contexts: ChildrenOutletContexts): void {
|
outletMap: RouterOutletMap): void {
|
||||||
const children: {[outlet: string]: any} = nodeChildrenAsMap(currNode);
|
const prevChildren: {[outlet: string]: any} = nodeChildrenAsMap(currNode);
|
||||||
futureNode.children.forEach(
|
futureNode.children.forEach(
|
||||||
c => { this.activateRoutes(c, children[c.value.outlet], contexts); });
|
c => { this.activateRoutes(c, prevChildren[c.value.outlet], outletMap); });
|
||||||
}
|
}
|
||||||
|
|
||||||
private activateRoutes(
|
private activateRoutes(
|
||||||
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>,
|
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>,
|
||||||
parentContexts: ChildrenOutletContexts): void {
|
parentOutletMap: RouterOutletMap): void {
|
||||||
const future = futureNode.value;
|
const future = futureNode.value;
|
||||||
const curr = currNode ? currNode.value : null;
|
const curr = currNode ? currNode.value : null;
|
||||||
|
|
||||||
|
@ -1114,50 +1108,42 @@ class ActivateRoutes {
|
||||||
if (future === curr) {
|
if (future === curr) {
|
||||||
if (future.component) {
|
if (future.component) {
|
||||||
// If we have a normal route, we need to go through an outlet.
|
// If we have a normal route, we need to go through an outlet.
|
||||||
const context = parentContexts.getOrCreateContext(future.outlet);
|
const outlet = getOutlet(parentOutletMap, future);
|
||||||
this.activateChildRoutes(futureNode, currNode, context.children);
|
this.activateChildRoutes(futureNode, currNode, outlet.outletMap);
|
||||||
} else {
|
} else {
|
||||||
// if we have a componentless route, we recurse but keep the same outlet map.
|
// if we have a componentless route, we recurse but keep the same outlet map.
|
||||||
this.activateChildRoutes(futureNode, currNode, parentContexts);
|
this.activateChildRoutes(futureNode, currNode, parentOutletMap);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (future.component) {
|
if (future.component) {
|
||||||
// if we have a normal route, we need to place the component into the outlet and recurse.
|
// if we have a normal route, we need to place the component into the outlet and recurse.
|
||||||
const context = parentContexts.getOrCreateContext(future.outlet);
|
const outlet = getOutlet(parentOutletMap, futureNode.value);
|
||||||
|
|
||||||
if (this.routeReuseStrategy.shouldAttach(future.snapshot)) {
|
if (this.routeReuseStrategy.shouldAttach(future.snapshot)) {
|
||||||
const stored =
|
const stored =
|
||||||
(<DetachedRouteHandleInternal>this.routeReuseStrategy.retrieve(future.snapshot));
|
(<DetachedRouteHandleInternal>this.routeReuseStrategy.retrieve(future.snapshot));
|
||||||
this.routeReuseStrategy.store(future.snapshot, null);
|
this.routeReuseStrategy.store(future.snapshot, null);
|
||||||
context.children.onOutletReAttached(stored.contexts);
|
outlet.attach(stored.componentRef, stored.route.value);
|
||||||
context.attachRef = stored.componentRef;
|
|
||||||
context.route = stored.route.value;
|
|
||||||
if (context.outlet) {
|
|
||||||
// Attach right away when the outlet has already been instantiated
|
|
||||||
// Otherwise attach from `RouterOutlet.ngOnInit` when it is instantiated
|
|
||||||
context.outlet.attach(stored.componentRef, stored.route.value);
|
|
||||||
}
|
|
||||||
advanceActivatedRouteNodeAndItsChildren(stored.route);
|
advanceActivatedRouteNodeAndItsChildren(stored.route);
|
||||||
} else {
|
} else {
|
||||||
const config = parentLoadedConfig(future.snapshot);
|
const outletMap = new RouterOutletMap();
|
||||||
const cmpFactoryResolver = config ? config.module.componentFactoryResolver : null;
|
this.placeComponentIntoOutlet(outletMap, future, outlet);
|
||||||
|
this.activateChildRoutes(futureNode, null, outletMap);
|
||||||
context.route = future;
|
|
||||||
context.resolver = cmpFactoryResolver;
|
|
||||||
if (context.outlet) {
|
|
||||||
// Activate the outlet when it has already been instantiated
|
|
||||||
// Otherwise it will get activated from its `ngOnInit` when instantiated
|
|
||||||
context.outlet.activateWith(future, cmpFactoryResolver);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.activateChildRoutes(futureNode, null, context.children);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if we have a componentless route, we recurse but keep the same outlet map.
|
// if we have a componentless route, we recurse but keep the same outlet map.
|
||||||
this.activateChildRoutes(futureNode, null, parentContexts);
|
this.activateChildRoutes(futureNode, null, parentOutletMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private placeComponentIntoOutlet(
|
||||||
|
outletMap: RouterOutletMap, future: ActivatedRoute, outlet: RouterOutlet): void {
|
||||||
|
const config = parentLoadedConfig(future.snapshot);
|
||||||
|
const cmpFactoryResolver = config ? config.module.componentFactoryResolver : null;
|
||||||
|
|
||||||
|
outlet.activateWith(future, cmpFactoryResolver, outletMap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function advanceActivatedRouteNodeAndItsChildren(node: TreeNode<ActivatedRoute>): void {
|
function advanceActivatedRouteNodeAndItsChildren(node: TreeNode<ActivatedRoute>): void {
|
||||||
|
@ -1197,6 +1183,19 @@ function nodeChildrenAsMap<T extends{outlet: string}>(node: TreeNode<T>| null) {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOutlet(outletMap: RouterOutletMap, route: ActivatedRoute): RouterOutlet {
|
||||||
|
const outlet = outletMap._outlets[route.outlet];
|
||||||
|
if (!outlet) {
|
||||||
|
const componentName = (<any>route.component).name;
|
||||||
|
if (route.outlet === PRIMARY_OUTLET) {
|
||||||
|
throw new Error(`Cannot find primary outlet to load '${componentName}'`);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Cannot find the outlet ${route.outlet} to load '${componentName}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outlet;
|
||||||
|
}
|
||||||
|
|
||||||
function validateCommands(commands: string[]): void {
|
function validateCommands(commands: string[]): void {
|
||||||
for (let i = 0; i < commands.length; i++) {
|
for (let i = 0; i < commands.length; i++) {
|
||||||
const cmd = commands[i];
|
const cmd = commands[i];
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {RouterOutlet} from './directives/router_outlet';
|
||||||
import {RouteReuseStrategy} from './route_reuse_strategy';
|
import {RouteReuseStrategy} from './route_reuse_strategy';
|
||||||
import {ErrorHandler, Router} from './router';
|
import {ErrorHandler, Router} from './router';
|
||||||
import {ROUTES} from './router_config_loader';
|
import {ROUTES} from './router_config_loader';
|
||||||
import {ChildrenOutletContexts} from './router_outlet_context';
|
import {RouterOutletMap} from './router_outlet_map';
|
||||||
import {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
|
import {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
|
||||||
import {ActivatedRoute} from './router_state';
|
import {ActivatedRoute} from './router_state';
|
||||||
import {UrlHandlingStrategy} from './url_handling_strategy';
|
import {UrlHandlingStrategy} from './url_handling_strategy';
|
||||||
|
@ -51,12 +51,12 @@ export const ROUTER_PROVIDERS: Provider[] = [
|
||||||
provide: Router,
|
provide: Router,
|
||||||
useFactory: setupRouter,
|
useFactory: setupRouter,
|
||||||
deps: [
|
deps: [
|
||||||
ApplicationRef, UrlSerializer, ChildrenOutletContexts, Location, Injector,
|
ApplicationRef, UrlSerializer, RouterOutletMap, Location, Injector, NgModuleFactoryLoader,
|
||||||
NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION,
|
Compiler, ROUTES, ROUTER_CONFIGURATION, [UrlHandlingStrategy, new Optional()],
|
||||||
[UrlHandlingStrategy, new Optional()], [RouteReuseStrategy, new Optional()]
|
[RouteReuseStrategy, new Optional()]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
ChildrenOutletContexts,
|
RouterOutletMap,
|
||||||
{provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
|
{provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
|
||||||
{provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader},
|
{provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader},
|
||||||
RouterPreloader,
|
RouterPreloader,
|
||||||
|
@ -270,12 +270,12 @@ export interface ExtraOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupRouter(
|
export function setupRouter(
|
||||||
ref: ApplicationRef, urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts,
|
ref: ApplicationRef, urlSerializer: UrlSerializer, outletMap: RouterOutletMap,
|
||||||
location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler,
|
location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler,
|
||||||
config: Route[][], opts: ExtraOptions = {}, urlHandlingStrategy?: UrlHandlingStrategy,
|
config: Route[][], opts: ExtraOptions = {}, urlHandlingStrategy?: UrlHandlingStrategy,
|
||||||
routeReuseStrategy?: RouteReuseStrategy) {
|
routeReuseStrategy?: RouteReuseStrategy) {
|
||||||
const router = new Router(
|
const router = new Router(
|
||||||
null, urlSerializer, contexts, location, injector, loader, compiler, flatten(config));
|
null, urlSerializer, outletMap, location, injector, loader, compiler, flatten(config));
|
||||||
|
|
||||||
if (urlHandlingStrategy) {
|
if (urlHandlingStrategy) {
|
||||||
router.urlHandlingStrategy = urlHandlingStrategy;
|
router.urlHandlingStrategy = urlHandlingStrategy;
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {ComponentFactoryResolver, ComponentRef} from '@angular/core';
|
|
||||||
|
|
||||||
import {RouterOutlet} from './directives/router_outlet';
|
|
||||||
import {ActivatedRoute} from './router_state';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store contextual information about a {@link RouterOutlet}
|
|
||||||
*
|
|
||||||
* @stable
|
|
||||||
*/
|
|
||||||
export class OutletContext {
|
|
||||||
outlet: RouterOutlet|null = null;
|
|
||||||
route: ActivatedRoute|null = null;
|
|
||||||
resolver: ComponentFactoryResolver|null = null;
|
|
||||||
children = new ChildrenOutletContexts();
|
|
||||||
attachRef: ComponentRef<any>|null = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store contextual information about the children (= nested) {@link RouterOutlet}
|
|
||||||
*
|
|
||||||
* @stable
|
|
||||||
*/
|
|
||||||
export class ChildrenOutletContexts {
|
|
||||||
// contexts for child outlets, by name.
|
|
||||||
private contexts = new Map<string, OutletContext>();
|
|
||||||
|
|
||||||
/** Called when a `RouterOutlet` directive is instantiated */
|
|
||||||
onChildOutletCreated(childName: string, outlet: RouterOutlet): void {
|
|
||||||
const context = this.getOrCreateContext(childName);
|
|
||||||
context.outlet = outlet;
|
|
||||||
this.contexts.set(childName, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a `RouterOutlet` directive is destroyed.
|
|
||||||
* We need to keep the context as the outlet could be destroyed inside a NgIf and might be
|
|
||||||
* re-created later.
|
|
||||||
*/
|
|
||||||
onChildOutletDestroyed(childName: string): void {
|
|
||||||
const context = this.getContext(childName);
|
|
||||||
if (context) {
|
|
||||||
context.outlet = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the corresponding route is deactivated during navigation.
|
|
||||||
* Because the component get destroyed, all children outlet are destroyed.
|
|
||||||
*/
|
|
||||||
onOutletDeactivated(): Map<string, OutletContext> {
|
|
||||||
const contexts = this.contexts;
|
|
||||||
this.contexts = new Map();
|
|
||||||
return contexts;
|
|
||||||
}
|
|
||||||
|
|
||||||
onOutletReAttached(contexts: Map<string, OutletContext>) { this.contexts = contexts; }
|
|
||||||
|
|
||||||
getOrCreateContext(childName: string): OutletContext {
|
|
||||||
let context = this.getContext(childName);
|
|
||||||
|
|
||||||
if (!context) {
|
|
||||||
context = new OutletContext();
|
|
||||||
this.contexts.set(childName, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
getContext(childName: string): OutletContext|null { return this.contexts.get(childName) || null; }
|
|
||||||
}
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {RouterOutlet} from './directives/router_outlet';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Contains all the router outlets created in a component.
|
||||||
|
*
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
export class RouterOutletMap {
|
||||||
|
/** @internal */
|
||||||
|
_outlets: {[name: string]: RouterOutlet} = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an outlet to this map.
|
||||||
|
*/
|
||||||
|
registerOutlet(name: string, outlet: RouterOutlet): void { this._outlets[name] = outlet; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an outlet from this map.
|
||||||
|
*/
|
||||||
|
removeOutlet(name: string): void { this._outlets[name] = undefined as any; }
|
||||||
|
}
|
|
@ -120,10 +120,8 @@ describe('Integration', () => {
|
||||||
router.resetConfig([{
|
router.resetConfig([{
|
||||||
path: 'parent/:id',
|
path: 'parent/:id',
|
||||||
component: Parent,
|
component: Parent,
|
||||||
children: [
|
children:
|
||||||
{path: 'child1', component: Child1},
|
[{path: 'child1', component: Child1}, {path: 'child2', component: Child2}]
|
||||||
{path: 'child2', component: Child2},
|
|
||||||
]
|
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
router.navigateByUrl('/parent/1/child1');
|
router.navigateByUrl('/parent/1/child1');
|
||||||
|
@ -133,18 +131,13 @@ describe('Integration', () => {
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(location.path()).toEqual('/parent/2/child2');
|
expect(location.path()).toEqual('/parent/2/child2');
|
||||||
expect(log).toEqual([
|
expect(log).toEqual([{id: '1'}, 'child1 destroy', {id: '2'}, 'child2 constructor']);
|
||||||
{id: '1'},
|
|
||||||
'child1 destroy',
|
|
||||||
{id: '2'},
|
|
||||||
'child2 constructor',
|
|
||||||
]);
|
|
||||||
})));
|
})));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should execute navigations serialy',
|
it('should execute navigations serialy',
|
||||||
fakeAsync(inject([Router, Location], (router: Router) => {
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig([
|
router.resetConfig([
|
||||||
|
@ -209,7 +202,7 @@ describe('Integration', () => {
|
||||||
|
|
||||||
router.resetConfig([{
|
router.resetConfig([{
|
||||||
path: 'child',
|
path: 'child',
|
||||||
component: OutletInNgIf,
|
component: LinkInNgIf,
|
||||||
children: [{path: 'simple', component: SimpleCmp}]
|
children: [{path: 'simple', component: SimpleCmp}]
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
@ -219,10 +212,10 @@ describe('Integration', () => {
|
||||||
expect(location.path()).toEqual('/child/simple');
|
expect(location.path()).toEqual('/child/simple');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should work when an outlet is added/removed', fakeAsync(() => {
|
it('should work when an outlet is in an ngIf (and is removed)', fakeAsync(() => {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'someRoot',
|
selector: 'someRoot',
|
||||||
template: `[<div *ngIf="cond"><router-outlet></router-outlet></div>]`
|
template: `<div *ngIf="cond"><router-outlet></router-outlet></div>`
|
||||||
})
|
})
|
||||||
class RootCmpWithLink {
|
class RootCmpWithLink {
|
||||||
cond: boolean = true;
|
cond: boolean = true;
|
||||||
|
@ -230,25 +223,26 @@ describe('Integration', () => {
|
||||||
TestBed.configureTestingModule({declarations: [RootCmpWithLink]});
|
TestBed.configureTestingModule({declarations: [RootCmpWithLink]});
|
||||||
|
|
||||||
const router: Router = TestBed.get(Router);
|
const router: Router = TestBed.get(Router);
|
||||||
|
const location: Location = TestBed.get(Location);
|
||||||
|
|
||||||
const fixture = createRoot(router, RootCmpWithLink);
|
const fixture = createRoot(router, RootCmpWithLink);
|
||||||
|
|
||||||
router.resetConfig([
|
router.resetConfig(
|
||||||
{path: 'simple', component: SimpleCmp},
|
[{path: 'simple', component: SimpleCmp}, {path: 'blank', component: BlankCmp}]);
|
||||||
{path: 'blank', component: BlankCmp},
|
|
||||||
]);
|
|
||||||
|
|
||||||
router.navigateByUrl('/simple');
|
router.navigateByUrl('/simple');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(fixture.nativeElement).toHaveText('[simple]');
|
expect(location.path()).toEqual('/simple');
|
||||||
|
|
||||||
fixture.componentInstance.cond = false;
|
const instance = fixture.componentInstance;
|
||||||
|
instance.cond = false;
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(fixture.nativeElement).toHaveText('[]');
|
|
||||||
|
|
||||||
fixture.componentInstance.cond = true;
|
let recordedError: any = null;
|
||||||
|
router.navigateByUrl('/blank') !.catch(e => recordedError = e);
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(fixture.nativeElement).toHaveText('[simple]');
|
|
||||||
|
expect(recordedError.message).toEqual('Cannot find primary outlet to load \'BlankCmp\'');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should update location when navigating', fakeAsync(() => {
|
it('should update location when navigating', fakeAsync(() => {
|
||||||
|
@ -3236,17 +3230,15 @@ describe('Integration', () => {
|
||||||
shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; }
|
shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; }
|
||||||
store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {}
|
store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {}
|
||||||
shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; }
|
shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; }
|
||||||
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle|null { return null; }
|
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { return null !; }
|
||||||
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
|
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
|
||||||
if (future.routeConfig !== curr.routeConfig) {
|
if (future.routeConfig !== curr.routeConfig) {
|
||||||
return false;
|
return false;
|
||||||
}
|
} else if (Object.keys(future.params).length !== Object.keys(curr.params).length) {
|
||||||
|
|
||||||
if (Object.keys(future.params).length !== Object.keys(curr.params).length) {
|
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
return Object.keys(future.params).every(k => future.params[k] === curr.params[k]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.keys(future.params).every(k => future.params[k] === curr.params[k]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3257,12 +3249,8 @@ describe('Integration', () => {
|
||||||
router.routeReuseStrategy = new AttachDetachReuseStrategy();
|
router.routeReuseStrategy = new AttachDetachReuseStrategy();
|
||||||
|
|
||||||
router.resetConfig([
|
router.resetConfig([
|
||||||
{
|
{path: 'a', component: TeamCmp, children: [{path: 'b', component: SimpleCmp}]},
|
||||||
path: 'a',
|
{path: 'c', component: UserCmp}
|
||||||
component: TeamCmp,
|
|
||||||
children: [{path: 'b', component: SimpleCmp}],
|
|
||||||
},
|
|
||||||
{path: 'c', component: UserCmp},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
router.navigateByUrl('/a/b');
|
router.navigateByUrl('/a/b');
|
||||||
|
@ -3471,7 +3459,7 @@ class RelativeLinkInIfCmp {
|
||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
{selector: 'child', template: '<div *ngIf="alwaysTrue"><router-outlet></router-outlet></div>'})
|
{selector: 'child', template: '<div *ngIf="alwaysTrue"><router-outlet></router-outlet></div>'})
|
||||||
class OutletInNgIf {
|
class LinkInNgIf {
|
||||||
alwaysTrue = true;
|
alwaysTrue = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3550,7 +3538,7 @@ function createRoot(router: Router, type: any): ComponentFixture<any> {
|
||||||
QueryParamsAndFragmentCmp,
|
QueryParamsAndFragmentCmp,
|
||||||
StringLinkButtonCmp,
|
StringLinkButtonCmp,
|
||||||
WrapperCmp,
|
WrapperCmp,
|
||||||
OutletInNgIf,
|
LinkInNgIf,
|
||||||
ComponentRecordingRoutePathAndUrl,
|
ComponentRecordingRoutePathAndUrl,
|
||||||
RouteCmp,
|
RouteCmp,
|
||||||
RootCmp,
|
RootCmp,
|
||||||
|
@ -3576,7 +3564,7 @@ function createRoot(router: Router, type: any): ComponentFixture<any> {
|
||||||
QueryParamsAndFragmentCmp,
|
QueryParamsAndFragmentCmp,
|
||||||
StringLinkButtonCmp,
|
StringLinkButtonCmp,
|
||||||
WrapperCmp,
|
WrapperCmp,
|
||||||
OutletInNgIf,
|
LinkInNgIf,
|
||||||
ComponentRecordingRoutePathAndUrl,
|
ComponentRecordingRoutePathAndUrl,
|
||||||
RouteCmp,
|
RouteCmp,
|
||||||
RootCmp,
|
RootCmp,
|
||||||
|
@ -3604,7 +3592,7 @@ function createRoot(router: Router, type: any): ComponentFixture<any> {
|
||||||
QueryParamsAndFragmentCmp,
|
QueryParamsAndFragmentCmp,
|
||||||
StringLinkButtonCmp,
|
StringLinkButtonCmp,
|
||||||
WrapperCmp,
|
WrapperCmp,
|
||||||
OutletInNgIf,
|
LinkInNgIf,
|
||||||
ComponentRecordingRoutePathAndUrl,
|
ComponentRecordingRoutePathAndUrl,
|
||||||
RouteCmp,
|
RouteCmp,
|
||||||
RootCmp,
|
RootCmp,
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {TestBed, inject} from '@angular/core/testing';
|
||||||
|
|
||||||
import {ResolveData} from '../src/config';
|
import {ResolveData} from '../src/config';
|
||||||
import {PreActivation, Router} from '../src/router';
|
import {PreActivation, Router} from '../src/router';
|
||||||
import {ChildrenOutletContexts} from '../src/router_outlet_context';
|
import {RouterOutletMap} from '../src/router_outlet_map';
|
||||||
import {ActivatedRouteSnapshot, RouterStateSnapshot, createEmptyStateSnapshot} from '../src/router_state';
|
import {ActivatedRouteSnapshot, RouterStateSnapshot, createEmptyStateSnapshot} from '../src/router_state';
|
||||||
import {DefaultUrlSerializer} from '../src/url_tree';
|
import {DefaultUrlSerializer} from '../src/url_tree';
|
||||||
import {TreeNode} from '../src/utils/tree';
|
import {TreeNode} from '../src/utils/tree';
|
||||||
|
@ -109,7 +109,7 @@ describe('Router', () => {
|
||||||
function checkResolveData(
|
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.traverse(new ChildrenOutletContexts());
|
p.traverse(new RouterOutletMap());
|
||||||
p.resolveData().subscribe(check, (e) => { throw e; });
|
p.resolveData().subscribe(check, (e) => { throw e; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {NoPreloading, PreloadingStrategy, ROUTES, Route, Router, RouterModule, RouterOutletMap, Routes, UrlHandlingStrategy, UrlSerializer, provideRoutes, ɵROUTER_PROVIDERS as ROUTER_PROVIDERS, ɵflatten as flatten} from '@angular/router';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,11 +82,11 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
export function setupTestingRouter(
|
export function setupTestingRouter(
|
||||||
urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location,
|
urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location,
|
||||||
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][],
|
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][],
|
||||||
urlHandlingStrategy?: UrlHandlingStrategy) {
|
urlHandlingStrategy?: UrlHandlingStrategy) {
|
||||||
const router = new Router(
|
const router = new Router(
|
||||||
null !, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes));
|
null !, urlSerializer, outletMap, location, injector, loader, compiler, flatten(routes));
|
||||||
if (urlHandlingStrategy) {
|
if (urlHandlingStrategy) {
|
||||||
router.urlHandlingStrategy = urlHandlingStrategy;
|
router.urlHandlingStrategy = urlHandlingStrategy;
|
||||||
}
|
}
|
||||||
|
@ -127,8 +127,8 @@ export function setupTestingRouter(
|
||||||
provide: Router,
|
provide: Router,
|
||||||
useFactory: setupTestingRouter,
|
useFactory: setupTestingRouter,
|
||||||
deps: [
|
deps: [
|
||||||
UrlSerializer, ChildrenOutletContexts, Location, NgModuleFactoryLoader, Compiler, Injector,
|
UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader, Compiler, Injector, ROUTES,
|
||||||
ROUTES, [UrlHandlingStrategy, new Optional()]
|
[UrlHandlingStrategy, new Optional()]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{provide: PreloadingStrategy, useExisting: NoPreloading}, provideRoutes([])
|
{provide: PreloadingStrategy, useExisting: NoPreloading}, provideRoutes([])
|
||||||
|
|
|
@ -59,16 +59,6 @@ export interface CanLoad {
|
||||||
canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean;
|
canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
|
||||||
export declare class ChildrenOutletContexts {
|
|
||||||
getContext(childName: string): OutletContext | null;
|
|
||||||
getOrCreateContext(childName: string): OutletContext;
|
|
||||||
onChildOutletCreated(childName: string, outlet: RouterOutlet): void;
|
|
||||||
onChildOutletDestroyed(childName: string): void;
|
|
||||||
onOutletDeactivated(): Map<string, OutletContext>;
|
|
||||||
onOutletReAttached(contexts: Map<string, OutletContext>): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare function convertToParamMap(params: Params): ParamMap;
|
export declare function convertToParamMap(params: Params): ParamMap;
|
||||||
|
|
||||||
|
@ -167,15 +157,6 @@ export declare class NoPreloading implements PreloadingStrategy {
|
||||||
preload(route: Route, fn: () => Observable<any>): Observable<any>;
|
preload(route: Route, fn: () => Observable<any>): Observable<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
|
||||||
export declare class OutletContext {
|
|
||||||
attachRef: ComponentRef<any> | null;
|
|
||||||
children: ChildrenOutletContexts;
|
|
||||||
outlet: RouterOutlet | null;
|
|
||||||
resolver: ComponentFactoryResolver | null;
|
|
||||||
route: ActivatedRoute | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export interface ParamMap {
|
export interface ParamMap {
|
||||||
readonly keys: string[];
|
readonly keys: string[];
|
||||||
|
@ -258,7 +239,7 @@ export declare class Router {
|
||||||
readonly routerState: RouterState;
|
readonly routerState: RouterState;
|
||||||
readonly url: string;
|
readonly url: string;
|
||||||
urlHandlingStrategy: UrlHandlingStrategy;
|
urlHandlingStrategy: UrlHandlingStrategy;
|
||||||
constructor(rootComponentType: Type<any> | null, urlSerializer: UrlSerializer, rootContexts: ChildrenOutletContexts, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes);
|
constructor(rootComponentType: Type<any> | null, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes);
|
||||||
createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, queryParamsHandling, preserveFragment}?: NavigationExtras): UrlTree;
|
createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, queryParamsHandling, preserveFragment}?: NavigationExtras): UrlTree;
|
||||||
dispose(): void;
|
dispose(): void;
|
||||||
initialNavigation(): void;
|
initialNavigation(): void;
|
||||||
|
@ -348,7 +329,7 @@ export declare class RouterModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class RouterOutlet implements OnDestroy, OnInit {
|
export declare class RouterOutlet implements OnDestroy {
|
||||||
activateEvents: EventEmitter<any>;
|
activateEvents: EventEmitter<any>;
|
||||||
readonly activatedRoute: ActivatedRoute;
|
readonly activatedRoute: ActivatedRoute;
|
||||||
readonly component: Object;
|
readonly component: Object;
|
||||||
|
@ -356,13 +337,20 @@ export declare class RouterOutlet implements OnDestroy, OnInit {
|
||||||
readonly isActivated: boolean;
|
readonly isActivated: boolean;
|
||||||
/** @deprecated */ readonly locationFactoryResolver: ComponentFactoryResolver;
|
/** @deprecated */ readonly locationFactoryResolver: ComponentFactoryResolver;
|
||||||
/** @deprecated */ readonly locationInjector: Injector;
|
/** @deprecated */ readonly locationInjector: Injector;
|
||||||
constructor(parentContexts: ChildrenOutletContexts, location: ViewContainerRef, resolver: ComponentFactoryResolver, name: string);
|
outletMap: RouterOutletMap;
|
||||||
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null): void;
|
constructor(parentOutletMap: RouterOutletMap, location: ViewContainerRef, resolver: ComponentFactoryResolver, name: string);
|
||||||
|
/** @deprecated */ activate(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver, injector: Injector, providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void;
|
||||||
|
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null, outletMap: RouterOutletMap): void;
|
||||||
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): void;
|
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): void;
|
||||||
deactivate(): void;
|
deactivate(): void;
|
||||||
detach(): ComponentRef<any>;
|
detach(): ComponentRef<any>;
|
||||||
ngOnDestroy(): void;
|
ngOnDestroy(): void;
|
||||||
ngOnInit(): void;
|
}
|
||||||
|
|
||||||
|
/** @stable */
|
||||||
|
export declare class RouterOutletMap {
|
||||||
|
registerOutlet(name: string, outlet: RouterOutlet): void;
|
||||||
|
removeOutlet(name: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
|
|
Loading…
Reference in New Issue