fix(router): RouterOutlet loads component twice in a race condition

Closes #7497

Closes #7545
This commit is contained in:
Misko Hevery 2016-03-10 20:42:32 -08:00 committed by Miško Hevery
parent d61aaac400
commit 2f581ffc88
3 changed files with 37 additions and 27 deletions

View File

@ -59,7 +59,7 @@ export abstract class ComponentRef {
* *
* TODO(i): rename to destroy to be consistent with AppViewManager and ViewContainerRef * TODO(i): rename to destroy to be consistent with AppViewManager and ViewContainerRef
*/ */
abstract dispose(); abstract dispose(): void;
} }
export class ComponentRef_ extends ComponentRef { export class ComponentRef_ extends ComponentRef {
@ -84,7 +84,7 @@ export class ComponentRef_ extends ComponentRef {
*/ */
get hostComponentType(): Type { return this.componentType; } get hostComponentType(): Type { return this.componentType; }
dispose() { this._dispose(); } dispose(): void { this._dispose(); }
} }
/** /**

View File

@ -34,7 +34,7 @@ let _resolveToTrue = PromiseWrapper.resolve(true);
@Directive({selector: 'router-outlet'}) @Directive({selector: 'router-outlet'})
export class RouterOutlet implements OnDestroy { export class RouterOutlet implements OnDestroy {
name: string = null; name: string = null;
private _componentRef: ComponentRef = null; private _componentRef: Promise<ComponentRef> = null;
private _currentInstruction: ComponentInstruction = null; private _currentInstruction: ComponentInstruction = null;
constructor(private _elementRef: ElementRef, private _loader: DynamicComponentLoader, constructor(private _elementRef: ElementRef, private _loader: DynamicComponentLoader,
@ -62,14 +62,17 @@ export class RouterOutlet implements OnDestroy {
provide(RouteParams, {useValue: new RouteParams(nextInstruction.params)}), provide(RouteParams, {useValue: new RouteParams(nextInstruction.params)}),
provide(routerMod.Router, {useValue: childRouter}) provide(routerMod.Router, {useValue: childRouter})
]); ]);
return this._loader.loadNextToLocation(componentType, this._elementRef, providers) this._componentRef =
.then((componentRef) => { this._loader.loadNextToLocation(componentType, this._elementRef, providers);
this._componentRef = componentRef; return this._componentRef.then((componentRef) => {
if (hasLifecycleHook(hookMod.routerOnActivate, componentType)) { if (hasLifecycleHook(hookMod.routerOnActivate, componentType)) {
return (<OnActivate>this._componentRef.instance) return this._componentRef.then(
.routerOnActivate(nextInstruction, previousInstruction); (ref: ComponentRef) =>
} (<OnActivate>ref.instance).routerOnActivate(nextInstruction, previousInstruction));
}); } else {
return componentRef;
}
});
} }
/** /**
@ -86,12 +89,14 @@ export class RouterOutlet implements OnDestroy {
// a new one. // a new one.
if (isBlank(this._componentRef)) { if (isBlank(this._componentRef)) {
return this.activate(nextInstruction); return this.activate(nextInstruction);
} else {
return PromiseWrapper.resolve(
hasLifecycleHook(hookMod.routerOnReuse, this._currentInstruction.componentType) ?
this._componentRef.then(
(ref: ComponentRef) =>
(<OnReuse>ref.instance).routerOnReuse(nextInstruction, previousInstruction)) :
true);
} }
return PromiseWrapper.resolve(
hasLifecycleHook(hookMod.routerOnReuse, this._currentInstruction.componentType) ?
(<OnReuse>this._componentRef.instance)
.routerOnReuse(nextInstruction, previousInstruction) :
true);
} }
/** /**
@ -102,14 +107,16 @@ export class RouterOutlet implements OnDestroy {
var next = _resolveToTrue; var next = _resolveToTrue;
if (isPresent(this._componentRef) && isPresent(this._currentInstruction) && if (isPresent(this._componentRef) && isPresent(this._currentInstruction) &&
hasLifecycleHook(hookMod.routerOnDeactivate, this._currentInstruction.componentType)) { hasLifecycleHook(hookMod.routerOnDeactivate, this._currentInstruction.componentType)) {
next = <Promise<boolean>>PromiseWrapper.resolve( next = this._componentRef.then(
(<OnDeactivate>this._componentRef.instance) (ref: ComponentRef) =>
.routerOnDeactivate(nextInstruction, this._currentInstruction)); (<OnDeactivate>ref.instance)
.routerOnDeactivate(nextInstruction, this._currentInstruction));
} }
return next.then((_) => { return next.then((_) => {
if (isPresent(this._componentRef)) { if (isPresent(this._componentRef)) {
this._componentRef.dispose(); var onDispose = this._componentRef.then((ref: ComponentRef) => ref.dispose());
this._componentRef = null; this._componentRef = null;
return onDispose;
} }
}); });
} }
@ -127,11 +134,13 @@ export class RouterOutlet implements OnDestroy {
return _resolveToTrue; return _resolveToTrue;
} }
if (hasLifecycleHook(hookMod.routerCanDeactivate, this._currentInstruction.componentType)) { if (hasLifecycleHook(hookMod.routerCanDeactivate, this._currentInstruction.componentType)) {
return <Promise<boolean>>PromiseWrapper.resolve( return this._componentRef.then(
(<CanDeactivate>this._componentRef.instance) (ref: ComponentRef) =>
.routerCanDeactivate(nextInstruction, this._currentInstruction)); (<CanDeactivate>ref.instance)
.routerCanDeactivate(nextInstruction, this._currentInstruction));
} else {
return _resolveToTrue;
} }
return _resolveToTrue;
} }
/** /**
@ -151,8 +160,9 @@ export class RouterOutlet implements OnDestroy {
this._currentInstruction.componentType != nextInstruction.componentType) { this._currentInstruction.componentType != nextInstruction.componentType) {
result = false; result = false;
} else if (hasLifecycleHook(hookMod.routerCanReuse, this._currentInstruction.componentType)) { } else if (hasLifecycleHook(hookMod.routerCanReuse, this._currentInstruction.componentType)) {
result = (<CanReuse>this._componentRef.instance) result = this._componentRef.then(
.routerCanReuse(nextInstruction, this._currentInstruction); (ref: ComponentRef) =>
(<CanReuse>ref.instance).routerCanReuse(nextInstruction, this._currentInstruction));
} else { } else {
result = nextInstruction == this._currentInstruction || result = nextInstruction == this._currentInstruction ||
(isPresent(nextInstruction.params) && isPresent(this._currentInstruction.params) && (isPresent(nextInstruction.params) && isPresent(this._currentInstruction.params) &&

View File

@ -106,7 +106,7 @@ const CORE = [
'ComponentMetadata.viewProviders:any[]', 'ComponentMetadata.viewProviders:any[]',
'ComponentRef', 'ComponentRef',
'ComponentRef.componentType:Type', 'ComponentRef.componentType:Type',
'ComponentRef.dispose():any', 'ComponentRef.dispose():void',
'ComponentRef.hostComponent:any', 'ComponentRef.hostComponent:any',
'ComponentRef.hostView:HostViewRef', 'ComponentRef.hostView:HostViewRef',
'ComponentRef.injector:Injector', 'ComponentRef.injector:Injector',