我假设route 是注入的ActivatedRoute。
每个ActivatedRoute 绑定到一个路由组件,当路由发生变化时,当前显示的组件将被销毁,以及它的绑定ActivatedRoute,这就是你不会收到complete 通知的原因。
Here'sActivatedRoute 是如何创建的:
function createActivatedRoute(c: ActivatedRouteSnapshot) {
return new ActivatedRoute(
new BehaviorSubject(c.url), new BehaviorSubject(c.params), new BehaviorSubject(c.queryParams),
new BehaviorSubject(c.fragment), new BehaviorSubject(c.data), c.outlet, c.component, c);
}
现在,ActivatedRoutes 是如何绑定到路由组件的?
假设您的配置如下所示:
{
path: 'a/:id',
component: AComponent,
children: [
{
path: 'b',
component: BComponent,
},
{
path: 'c',
component: CComponent,
},
]
}
以及发布的 URL,例如 a/123/b
你最终会拥有ActivatedRoutes 的树:
APP
|
A
|
B
每当您安排导航(例如router.navigateToUrl())时,它都必须经过一些重要阶段:
-
应用重定向:检查重定向;加载延迟加载的模块;查找
NoMatch 错误
-
识别:创建
ActivatedRouteSnapshot 树
-
预激活:将结果树与当前树进行比较;此阶段还收集
canActivate 和 canDeactivate 警卫,基于发现的差异
- 跑卫
-
创建路由器状态:创建
ActivatedRoute 树的位置
-
激活路线:这是蛋糕上的樱桃,也是利用ActivatedRoute树的地方
提到router-outlet 所扮演的角色也很重要。
Angular 在 Map 对象的帮助下跟踪 router-outlets。
Here's 在您的应用中有 <router-outlet></router-outlet> 时会发生什么:
@Directive({selector: 'router-outlet', exportAs: 'outlet'})
export class RouterOutlet implements OnDestroy, OnInit {
private activated: ComponentRef<any>|null = null;
private _activatedRoute: ActivatedRoute|null = null;
private name: string;
@Output('activate') activateEvents = new EventEmitter<any>();
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
constructor(
private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef,
private resolver: ComponentFactoryResolver, @Attribute('name') name: string,
private changeDetector: ChangeDetectorRef) {
this.name = name || PRIMARY_OUTLET;
parentContexts.onChildOutletCreated(this.name, this);
}
}
注意activated(它是一个组件)和_activatedRoute的存在!。
而here是ChildrenOutletContexts的相关位:
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);
}
}
其中childName 默认为'primary'。现在,只关注context.outlet 部分。
所以,对于我们的路由配置:
{
path: 'a/:id',
component: AComponent,
children: [
{
path: 'b',
component: BComponent,
},
{
path: 'c',
component: CComponent,
},
]
}
router-outlet Map 看起来像这样(大致):
{
primary: { // Where `AComponent` resides [1]
children: {
// Here `AComponent`'s children reside [2]
primary: { children: { } }
}
}
}
现在,让我们看看how a route is activated:
// This block of code will be run for [1] and [2] (in this order!)
const context = parentContexts.getOrCreateContext(future.outlet);
/* ... */
const config = parentLoadedConfig(future.snapshot);
const cmpFactoryResolver = config ? config.module.componentFactoryResolver : null;
context.attachRef = null;
context.route = future;
context.resolver = cmpFactoryResolver;
if (context.outlet) {
context.outlet.activateWith(future, cmpFactoryResolver);
}
this.activateChildRoutes(futureNode, null, context.children);
context.outlet.activateWith(future, cmpFactoryResolver); 是我们正在寻找的(其中outlet 是RouterOutlet 指令实例):
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
if (this.isActivated) {
throw new Error('Cannot activate an already activated outlet');
}
this._activatedRoute = activatedRoute;
const snapshot = activatedRoute._futureSnapshot;
const component = <any>snapshot.routeConfig!.component;
resolver = resolver || this.resolver;
const factory = resolver.resolveComponentFactory(component);
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
this.activated = this.location.createComponent(factory, this.location.length, injector);
// Calling `markForCheck` to make sure we will run the change detection when the
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
this.changeDetector.markForCheck();
this.activateEvents.emit(this.activated.instance);
}
请注意,this.activated 包含 路由组件(例如 AComponent),this._activatedRoute 包含此组件的 ActivatedRoute。
现在让我们看看what happens,当我们导航到另一条路线并且当前视图被破坏时:
deactivateRouteAndOutlet(
route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts): void {
const context = parentContexts.getContext(route.value.outlet);
if (context) {
const children: {[outletName: string]: any} = nodeChildrenAsMap(route);
const contexts = route.value.component ? context.children : parentContexts;
// Deactivate children first
forEach(children, (v: any, k: string) => this.deactivateRouteAndItsChildren(v, contexts));
if (context.outlet) {
// Destroy the component
context.outlet.deactivate();
// Destroy the contexts for all the outlets that were in the component
context.children.onOutletDeactivated();
}
}
}
RouterOutlet.deactivate() 看起来像like this:
deactivate(): void {
if (this.activated) {
const c = this.component;
this.activated.destroy(); // Destroying the current component
this.activated = null;
// Nulling out the activated route - so no `complete` notification
this._activatedRoute = null;
this.deactivateEvents.emit(c);
}
}