Angular2 - Directive - RouterOutlet

大段源码

/**
* A router outlet is a placeholder that Angular dynamically fills based on the application's route.
*
* ## Use
*
*
* <router-outlet></router-outlet>
*
*/
@Directive({selector: 'router-outlet'})
export class RouterOutlet {
name: string = null;
private _componentRef: ComponentRef = null;
private _currentInstruction: ComponentInstruction = null;

constructor(private _elementRef: ElementRef, private _loader: DynamicComponentLoader,
private _parentRouter: routerMod.Router, @Attribute('name') nameAttr: string) {
if (isPresent(nameAttr)) {
this.name = nameAttr;
this._parentRouter.registerAuxOutlet(this);
} else {
this._parentRouter.registerPrimaryOutlet(this);
}
}

/**
* Called by the Router to instantiate a new component during the commit phase of a navigation.
* This method in turn is responsible for calling the `routerOnActivate` hook of its child.
*/
activate(nextInstruction: ComponentInstruction): Promise<any> {
var previousInstruction = this._currentInstruction;
this._currentInstruction = nextInstruction;
var componentType = nextInstruction.componentType;
var childRouter = this._parentRouter.childRouter(componentType);

var providers = Injector.resolve([
provide(RouteData, {useValue: nextInstruction.routeData}),
provide(RouteParams, {useValue: new RouteParams(nextInstruction.params)}),
provide(routerMod.Router, {useValue: childRouter})
]);
return this._loader.loadNextToLocation(componentType, this._elementRef, providers)
.then((componentRef) => {
this._componentRef = componentRef;
if (hasLifecycleHook(hookMod.routerOnActivate, componentType)) {
return (<OnActivate>this._componentRef.instance)
.routerOnActivate(nextInstruction, previousInstruction);
}
});
}

/**
* Called by the {@link Router} during the commit phase of a navigation when an outlet
* reuses a component between different routes.
* This method in turn is responsible for calling the `routerOnReuse` hook of its child.
*/
reuse(nextInstruction: ComponentInstruction): Promise<any> {
var previousInstruction = this._currentInstruction;
this._currentInstruction = nextInstruction;

if (isBlank(this._componentRef)) {
throw new BaseException(`Cannot reuse an outlet that does not contain a component.`);
}
return PromiseWrapper.resolve(
hasLifecycleHook(hookMod.routerOnReuse, this._currentInstruction.componentType) ?
(<OnReuse>this._componentRef.instance)
.routerOnReuse(nextInstruction, previousInstruction) :
true);
}

/**
* Called by the {@link Router} when an outlet disposes of a component's contents.
* This method in turn is responsible for calling the `routerOnDeactivate` hook of its child.
*/
deactivate(nextInstruction: ComponentInstruction): Promise<any> {
var next = _resolveToTrue;
if (isPresent(this._componentRef) && isPresent(this._currentInstruction) &&
hasLifecycleHook(hookMod.routerOnDeactivate, this._currentInstruction.componentType)) {
next = PromiseWrapper.resolve(
(<OnDeactivate>this._componentRef.instance)
.routerOnDeactivate(nextInstruction, this._currentInstruction));
}
return next.then((_) => {
if (isPresent(this._componentRef)) {
this._componentRef.dispose();
this._componentRef = null;
}
});
}

/**
* Called by the {@link Router} during recognition phase of a navigation.
*
* If this resolves to `false`, the given navigation is cancelled.
*
* This method delegates to the child component's `routerCanDeactivate` hook if it exists,
* and otherwise resolves to true.
*/
routerCanDeactivate(nextInstruction: ComponentInstruction): Promise<boolean> {
if (isBlank(this._currentInstruction)) {
return _resolveToTrue;
}
if (hasLifecycleHook(hookMod.routerCanDeactivate, this._currentInstruction.componentType)) {
return PromiseWrapper.resolve(
(<CanDeactivate>this._componentRef.instance)
.routerCanDeactivate(nextInstruction, this._currentInstruction));
}
return _resolveToTrue;
}

/**
* Called by the {@link Router} during recognition phase of a navigation.
*
* If the new child component has a different Type than the existing child component,
* this will resolve to `false`. You can't reuse an old component when the new component
* is of a different Type.
*
* Otherwise, this method delegates to the child component's `routerCanReuse` hook if it exists,
* or resolves to true if the hook is not present.
*/

routerCanReuse(nextInstruction: ComponentInstruction): Promise<boolean> {
var result;

if (isBlank(this._currentInstruction) ||
this._currentInstruction.componentType != nextInstruction.componentType) {
result = false;
} else if (hasLifecycleHook(hookMod.routerCanReuse, this._currentInstruction.componentType)) {
result = (<CanReuse>this._componentRef.instance)
.routerCanReuse(nextInstruction, this._currentInstruction);
} else {
result = nextInstruction == this._currentInstruction ||
(isPresent(nextInstruction.params) && isPresent(this._currentInstruction.params) &&
StringMapWrapper.equals(nextInstruction.params, this._currentInstruction.params));
}
return PromiseWrapper.resolve(result);
}
}

画重点:

A router outlet is a placeholder that Angular dynamically fills based on the application’s route.

 
constructor(private _elementRef: ElementRef, private _loader: DynamicComponentLoader,
private _parentRouter: routerMod.Router, @Attribute('name') nameAttr: string) {
if (isPresent(nameAttr)) {
this.name = nameAttr;
this._parentRouter.registerAuxOutlet(this);
} else {
this._parentRouter.registerPrimaryOutlet(this);
}
}

有上面的注释和构造看,这个基本上指令内是一些 旧 component 向新component跳转过程中要处理的东西

name属性是针对 AuxOutlet的,以后再说

registerPrimaryOutlet - 是去更新此路由,可以理解为创建相应的Instruction tree


activate(nextInstruction: ComponentInstruction): Promise<any> {
var previousInstruction = this._currentInstruction;
this._currentInstruction = nextInstruction;
var componentType = nextInstruction.componentType;
var childRouter = this._parentRouter.childRouter(componentType);

var providers = Injector.resolve([
provide(RouteData, {useValue: nextInstruction.routeData}),
provide(RouteParams, {useValue: new RouteParams(nextInstruction.params)}),
provide(routerMod.Router, {useValue: childRouter})
]);
return this._loader.loadNextToLocation(componentType, this._elementRef, providers)
.then((componentRef) => {
this._componentRef = componentRef;
if (hasLifecycleHook(hookMod.routerOnActivate, componentType)) {
return (<OnActivate>this._componentRef.instance)
.routerOnActivate(nextInstruction, previousInstruction);
}
});
}

这块是新component即将加载的函数

hasLifecycleHook(hookMod.routerOnActivate 检测新component里对OnActivate hook 有没有定义, 有的话就要调用
OnActivate — 有单独的地方说

<OnActivate>this._componentRef.instance 这个类型转换单独说下,以为上面判断新component里有OnActivate 定义了,所以这个instance 肯定符合这个interface


reuse(nextInstruction: ComponentInstruction): Promise<any> {
var previousInstruction = this._currentInstruction;
this._currentInstruction = nextInstruction;

if (isBlank(this._componentRef)) {
throw new BaseException(`Cannot reuse an outlet that does not contain a component.`);
}
return PromiseWrapper.resolve(
hasLifecycleHook(hookMod.routerOnReuse, this._currentInstruction.componentType) ?
(<OnReuse>this._componentRef.instance)
.routerOnReuse(nextInstruction, previousInstruction) :
true);
}

同理这里是对OnReuse hook的检查和调用 isBlank(this._componentRef) 之前一定要经过activate 赋值的,也就是创建过的才可能reuse


deactivate(nextInstruction: ComponentInstruction): Promise<any> {
var next = _resolveToTrue;
if (isPresent(this._componentRef) && isPresent(this._currentInstruction) &&
hasLifecycleHook(hookMod.routerOnDeactivate, this._currentInstruction.componentType)) {
next = PromiseWrapper.resolve(
(<OnDeactivate>this._componentRef.instance)
.routerOnDeactivate(nextInstruction, this._currentInstruction));
}
return next.then((_) => {
if (isPresent(this._componentRef)) {
this._componentRef.dispose();
this._componentRef = null;
}
});
}

在当前component销毁前要做的检查 OnDeactivate hook,如果有先执行

next = PromiseWrapper.resolve(
(<OnDeactivate>this._componentRef.instance)
.routerOnDeactivate(nextInstruction, this._currentInstruction));


this._componentRef.dispose();
this._componentRef = null;


routerCanDeactivate(nextInstruction: ComponentInstruction): Promise<boolean> {
if (isBlank(this._currentInstruction)) {
return _resolveToTrue;
}
if (hasLifecycleHook(hookMod.routerCanDeactivate, this._currentInstruction.componentType)) {
return PromiseWrapper.resolve(
(<CanDeactivate>this._componentRef.instance)
.routerCanDeactivate(nextInstruction, this._currentInstruction));
}
return _resolveToTrue;
}

判断CanDeactivate hook

return PromiseWrapper.resolve(
(<CanDeactivate>this._componentRef.instance)
.routerCanDeactivate(nextInstruction, this._currentInstruction));

默认返回true 就是跳转, 如果这里return false 就是取消跳转


  routerCanReuse(nextInstruction: ComponentInstruction): Promise<boolean> {
var result;

if (isBlank(this._currentInstruction) ||
this._currentInstruction.componentType != nextInstruction.componentType) {
result = false;
} else if (hasLifecycleHook(hookMod.routerCanReuse, this._currentInstruction.componentType)) {
result = (<CanReuse>this._componentRef.instance)
.routerCanReuse(nextInstruction, this._currentInstruction);
} else {
result = nextInstruction == this._currentInstruction ||
(isPresent(nextInstruction.params) && isPresent(this._currentInstruction.params) &&
StringMapWrapper.equals(nextInstruction.params, this._currentInstruction.params));
}
return PromiseWrapper.resolve(result);
}
}

看的新component是否和 当前是一样,如果一样,就能reuse,不能就新建一个。
注释:

If the new child component has a different Type than the existing child component,
this will resolve to false. You can’t reuse an old component when the new component
is of a different Type.
Otherwise, this method delegates to the child component’s routerCanReuse hook if it exists,
or resolves to true if the hook is not present.

这个 RouterOutlet 就是个过渡站,判断中间过程的各种hook。公交车调度,项目协调。。。

一个问题

什么时候 reuse
前提是多个路由里用了相同的组件, 这个时候用的是同一个instance
当然你可以变化一些参数如官网的例子

当你在CanReuse return true后, 在再
routerOnReuse 中传递新的变化参数。 不同路由间相同组件的通信手段

routerOnReuse(next: ComponentInstruction, prev: ComponentInstruction) {
this.name = next.params['name'];
}