Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 39 additions & 22 deletions modules/angular2/src/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,30 @@ export class Router {
throw new BaseException(`registerPrimaryOutlet expects to be called with an unnamed outlet.`);
}

if (isPresent(this._outlet)) {
throw new BaseException(`Primary outlet is already registered.`);
}

this._outlet = outlet;
if (isPresent(this._currentInstruction)) {
return this.commit(this._currentInstruction, false);
}
return _resolveToTrue;
}

/**
* Unregister an outlet (because it was destroyed, etc).
*
* You probably don't need to use this unless you're writing a custom outlet implementation.
*/
unregisterPrimaryOutlet(outlet: RouterOutlet): void {
if (isPresent(outlet.name)) {
throw new BaseException(`registerPrimaryOutlet expects to be called with an unnamed outlet.`);
}
this._outlet = null;
}


/**
* Register an outlet to notified of auxiliary route changes.
*
Expand Down Expand Up @@ -197,28 +214,6 @@ export class Router {
});
}

/** @internal */
_navigate(instruction: Instruction, _skipLocationChange: boolean): Promise<any> {
return this._settleInstruction(instruction)
.then((_) => this._routerCanReuse(instruction))
.then((_) => this._canActivate(instruction))
.then((result: boolean) => {
if (!result) {
return false;
}
return this._routerCanDeactivate(instruction)
.then((result: boolean) => {
if (result) {
return this.commit(instruction, _skipLocationChange)
.then((_) => {
this._emitNavigationFinish(instruction.toRootUrl());
return true;
});
}
});
});
}

/** @internal */
_settleInstruction(instruction: Instruction): Promise<any> {
return instruction.resolveComponent().then((_) => {
Expand All @@ -239,6 +234,28 @@ export class Router {
});
}

/** @internal */
_navigate(instruction: Instruction, _skipLocationChange: boolean): Promise<any> {
return this._settleInstruction(instruction)
.then((_) => this._routerCanReuse(instruction))
.then((_) => this._canActivate(instruction))
.then((result: boolean) => {
if (!result) {
return false;
}
return this._routerCanDeactivate(instruction)
.then((result: boolean) => {
if (result) {
return this.commit(instruction, _skipLocationChange)
.then((_) => {
this._emitNavigationFinish(instruction.toRootUrl());
return true;
});
}
});
});
}

private _emitNavigationFinish(url): void { ObservableWrapper.callEmit(this._subject, url); }

private _afterPromiseFinishNavigating(promise: Promise<any>): Promise<any> {
Expand Down
15 changes: 11 additions & 4 deletions modules/angular2/src/router/router_outlet.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {PromiseWrapper} from 'angular2/src/facade/async';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';

import {
Directive,
Expand All @@ -11,7 +10,8 @@ import {
ElementRef,
Injector,
provide,
Dependency
Dependency,
OnDestroy
} from 'angular2/core';

import * as routerMod from './router';
Expand All @@ -32,7 +32,7 @@ let _resolveToTrue = PromiseWrapper.resolve(true);
* ```
*/
@Directive({selector: 'router-outlet'})
export class RouterOutlet {
export class RouterOutlet implements OnDestroy {
name: string = null;
private _componentRef: ComponentRef = null;
private _currentInstruction: ComponentInstruction = null;
Expand Down Expand Up @@ -81,8 +81,11 @@ export class RouterOutlet {
var previousInstruction = this._currentInstruction;
this._currentInstruction = nextInstruction;

// it's possible the component is removed before it can be reactivated (if nested withing
// another dynamically loaded component, for instance). In that case, we simply activate
// a new one.
if (isBlank(this._componentRef)) {
throw new BaseException(`Cannot reuse an outlet that does not contain a component.`);
return this.activate(nextInstruction);
}
return PromiseWrapper.resolve(
hasLifecycleHook(hookMod.routerOnReuse, this._currentInstruction.componentType) ?
Expand Down Expand Up @@ -157,4 +160,8 @@ export class RouterOutlet {
}
return PromiseWrapper.resolve(result);
}

ngOnDestroy(): void {
this._parentRouter.unregisterPrimaryOutlet(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
ROUTER_DIRECTIVES
} from 'angular2/router';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {isPresent} from 'angular2/src/facade/lang';
import {DynamicComponentLoader, ComponentRef} from 'angular2/src/core/linker/dynamic_component_loader';
import {ElementRef} from 'angular2/src/core/linker/element_ref';

@Component({selector: 'goodbye-cmp', template: `{{farewell}}`})
export class GoodbyeCmp {
Expand Down Expand Up @@ -135,3 +138,31 @@ export function asyncRouteDataCmp() {
@RouteConfig([new Redirect({path: '/child-redirect', redirectTo: ['../HelloSib']})])
export class RedirectToParentCmp {
}


@Component({selector: 'dynamic-loader-cmp', template: `{ <div #viewport></div> }`})
@RouteConfig([
new Route({path: '/', component: HelloCmp})
])
export class DynamicLoaderCmp {
private _componentRef: ComponentRef = null;
constructor(private _dynamicComponentLoader: DynamicComponentLoader, private _elementRef: ElementRef) {}

onSomeAction(): Promise<any> {
if (isPresent(this._componentRef)) {
this._componentRef.dispose();
this._componentRef = null;
}
return this._dynamicComponentLoader.loadIntoLocation(DynamicallyLoadedComponent, this._elementRef, 'viewport').then((cmp) => {
this._componentRef = cmp;
});
}
}


@Component({
selector: 'loaded-cmp',
template: '<router-outlet></router-outlet>',
directives: [ROUTER_DIRECTIVES]
})
class DynamicallyLoadedComponent {}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {specs, compile, TEST_ROUTER_PROVIDERS, clickOnElement, getHref} from '..
import {By} from 'angular2/platform/common_dom';
import {Router, Route, Location} from 'angular2/router';

import {HelloCmp, UserCmp, TeamCmp, ParentCmp, ParentWithDefaultCmp} from './fixture_components';
import {HelloCmp, UserCmp, TeamCmp, ParentCmp, ParentWithDefaultCmp, DynamicLoaderCmp} from './fixture_components';


function getLinkElement(rtc: ComponentFixture) {
Expand Down Expand Up @@ -420,6 +420,50 @@ function syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams() {
}));
}

function syncRoutesWithDynamicComponents() {
var fixture;
var tcb;
var rtr : Router;

beforeEachProviders(() => TEST_ROUTER_PROVIDERS);

beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
tcb = tcBuilder;
rtr = router;
}));

it('should work', inject([AsyncTestCompleter], (async) => {
tcb.createAsync(DynamicLoaderCmp)
.then((rtc) => {fixture = rtc})
.then(
(_) => rtr.config(
[new Route({path: '/', component: HelloCmp})]))
.then((_) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText('{ }');
return fixture.componentInstance.onSomeAction();
}).then((_) => {
fixture.detectChanges();
return rtr.navigateByUrl('/');
}).then((_) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText('{ hello }');

return fixture.componentInstance.onSomeAction();
}).then((_) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText('{ hello }');
async.done();
})
}));

}



export function registerSpecs() {
specs['syncRoutesWithoutChildrenWithoutParams'] = syncRoutesWithoutChildrenWithoutParams;
specs['syncRoutesWithoutChildrenWithParams'] = syncRoutesWithoutChildrenWithParams;
Expand All @@ -429,4 +473,5 @@ export function registerSpecs() {
syncRoutesWithSyncChildrenWithoutDefaultRoutesWithParams;
specs['syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams'] =
syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams;
specs['syncRoutesWithDynamicComponents'] = syncRoutesWithDynamicComponents;
}
2 changes: 2 additions & 0 deletions modules/angular2/test/router/integration/sync_route_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ export function main() {
describeWith('default routes', () => { describeWithout('params', itShouldRoute); });

});

describeWith('dynamic components', itShouldRoute);
});
}