Skip to content

Commit

Permalink
feat(router): support links with just auxiliary routes
Browse files Browse the repository at this point in the history
  • Loading branch information
btford committed Dec 16, 2015
1 parent 909e70b commit 2a2f9a9
Show file tree
Hide file tree
Showing 12 changed files with 466 additions and 285 deletions.
4 changes: 4 additions & 0 deletions modules/angular1_router/lib/facades.es5
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ var ListWrapper = {
return array[0];
},

last: function(array) {
return (array && array.length) > 0 ? array[array.length - 1] : null;
},

map: function (l, fn) {
return l.map(fn);
},
Expand Down
8 changes: 7 additions & 1 deletion modules/angular2/src/router/component_recognizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
AbstractRecognizer,
RouteRecognizer,
RedirectRecognizer,
RouteMatch
RouteMatch,
PathMatch
} from './route_recognizer';
import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl';
import {AsyncRouteHandler} from './async_route_handler';
Expand Down Expand Up @@ -117,6 +118,11 @@ export class ComponentRecognizer {
}
});

// handle cases where we are routing just to an aux route
if (solutions.length == 0 && isPresent(urlParse) && urlParse.auxiliary.length > 0) {
return [PromiseWrapper.resolve(new PathMatch(null, null, urlParse.auxiliary))];
}

return solutions;
}

Expand Down
6 changes: 3 additions & 3 deletions modules/angular2/src/router/instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ export abstract class Instruction {
public child: Instruction;
public auxInstruction: {[key: string]: Instruction} = {};

get urlPath(): string { return this.component.urlPath; }
get urlPath(): string { return isPresent(this.component) ? this.component.urlPath : ''; }

get urlParams(): string[] { return this.component.urlParams; }
get urlParams(): string[] { return isPresent(this.component) ? this.component.urlParams : []; }

get specificity(): number {
var total = 0;
Expand Down Expand Up @@ -181,7 +181,7 @@ export abstract class Instruction {

/** @internal */
_stringifyMatrixParams(): string {
return this.urlParams.length > 0 ? (';' + this.component.urlParams.join(';')) : '';
return this.urlParams.length > 0 ? (';' + this.urlParams.join(';')) : '';
}

/** @internal */
Expand Down
274 changes: 154 additions & 120 deletions modules/angular2/src/router/route_registry.ts

Large diffs are not rendered by default.

30 changes: 17 additions & 13 deletions modules/angular2/src/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ export class Router {
throw new BaseException(`registerAuxOutlet expects to be called with an outlet with a name.`);
}

// TODO...
// what is the host of an aux route???
var router = this.auxRouter(this.hostComponent);

this._auxRouters.set(outletName, router);
Expand Down Expand Up @@ -224,10 +222,12 @@ export class Router {
/** @internal */
_settleInstruction(instruction: Instruction): Promise<any> {
return instruction.resolveComponent().then((_) => {
instruction.component.reuse = false;

var unsettledInstructions: Array<Promise<any>> = [];

if (isPresent(instruction.component)) {
instruction.component.reuse = false;
}

if (isPresent(instruction.child)) {
unsettledInstructions.push(this._settleInstruction(instruction.child));
}
Expand Down Expand Up @@ -256,6 +256,9 @@ export class Router {
if (isBlank(this._outlet)) {
return _resolveToFalse;
}
if (isBlank(instruction.component)) {
return _resolveToTrue;
}
return this._outlet.routerCanReuse(instruction.component)
.then((result) => {
instruction.component.reuse = result;
Expand All @@ -280,7 +283,7 @@ export class Router {
if (isPresent(instruction)) {
childInstruction = instruction.child;
componentInstruction = instruction.component;
reuse = instruction.component.reuse;
reuse = isBlank(instruction.component) || instruction.component.reuse;
}
if (reuse) {
next = _resolveToTrue;
Expand All @@ -304,8 +307,9 @@ export class Router {
*/
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
this._currentInstruction = instruction;

var next: Promise<any> = _resolveToTrue;
if (isPresent(this._outlet)) {
if (isPresent(this._outlet) && isPresent(instruction.component)) {
var componentInstruction = instruction.component;
if (componentInstruction.reuse) {
next = this._outlet.reuse(componentInstruction);
Expand Down Expand Up @@ -381,15 +385,12 @@ export class Router {
}

private _getAncestorInstructions(): Instruction[] {
var ancestorComponents = [];
var ancestorInstructions = [this._currentInstruction];
var ancestorRouter: Router = this;
while (isPresent(ancestorRouter.parent) &&
isPresent(ancestorRouter.parent._currentInstruction)) {
ancestorRouter = ancestorRouter.parent;
ancestorComponents.unshift(ancestorRouter._currentInstruction);
while (isPresent(ancestorRouter = ancestorRouter.parent)) {
ancestorInstructions.unshift(ancestorRouter._currentInstruction);
}

return ancestorComponents;
return ancestorInstructions;
}


Expand Down Expand Up @@ -505,6 +506,9 @@ class ChildRouter extends Router {
function canActivateOne(nextInstruction: Instruction,
prevInstruction: Instruction): Promise<boolean> {
var next = _resolveToTrue;
if (isBlank(nextInstruction.component)) {
return next;
}
if (isPresent(nextInstruction.child)) {
next = canActivateOne(nextInstruction.child,
isPresent(prevInstruction) ? prevInstruction.child : null);
Expand Down
18 changes: 13 additions & 5 deletions modules/angular2/src/router/router_link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,24 @@ export class RouterLink {
// the instruction passed to the router to navigate
private _navigationInstruction: Instruction;

constructor(private _router: Router, private _location: Location) {}
constructor(private _router: Router, private _location: Location) {
// we need to update the link whenever a route changes to account for aux routes
this._router.subscribe((_) => this._updateLink());
}

// because auxiliary links take existing primary and auxiliary routes into account,
// we need to update the link whenever params or other routes change.
private _updateLink(): void {
this._navigationInstruction = this._router.generate(this._routeParams);
var navigationHref = this._navigationInstruction.toLinkUrl();
this.visibleHref = this._location.prepareExternalUrl(navigationHref);
}

get isRouteActive(): boolean { return this._router.isRouteActive(this._navigationInstruction); }

set routeParams(changes: any[]) {
this._routeParams = changes;
this._navigationInstruction = this._router.generate(this._routeParams);

var navigationHref = this._navigationInstruction.toLinkUrl();
this.visibleHref = this._location.prepareExternalUrl(navigationHref);
this._updateLink();
}

onClick(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {registerSpecs} from './impl/async_route_spec_impl';
export function main() {
registerSpecs();

ddescribeRouter('async routes', () => {
describeRouter('async routes', () => {
describeWithout('children', () => {
describeWith('route data', itShouldRoute);
describeWithAndWithout('params', itShouldRoute);
Expand Down
150 changes: 12 additions & 138 deletions modules/angular2/test/router/integration/auxiliary_route_spec.ts
Original file line number Diff line number Diff line change
@@ -1,145 +1,19 @@
import {
ComponentFixture,
AsyncTestCompleter,
TestComponentBuilder,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
expect,
iit,
inject,
beforeEachProviders,
it,
xit
} from 'angular2/testing_internal';
describeRouter,
ddescribeRouter,
describeWith,
describeWithout,
describeWithAndWithout,
itShouldRoute
} from './util';

import {provide, Component, Injector, Inject} from 'angular2/core';

import {Router, ROUTER_DIRECTIVES, RouteParams, RouteData, Location} from 'angular2/router';
import {RouteConfig, Route, AuxRoute, Redirect} from 'angular2/src/router/route_config_decorator';

import {TEST_ROUTER_PROVIDERS, RootCmp, compile, clickOnElement, getHref} from './util';

function getLinkElement(rtc: ComponentFixture) {
return rtc.debugElement.componentViewChildren[0].nativeElement;
}

var cmpInstanceCount;
var childCmpInstanceCount;
import {registerSpecs} from './impl/aux_route_spec_impl';

export function main() {
describe('auxiliary routes', () => {

var tcb: TestComponentBuilder;
var fixture: ComponentFixture;
var rtr;

beforeEachProviders(() => TEST_ROUTER_PROVIDERS);

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

it('should recognize and navigate from the URL', inject([AsyncTestCompleter], (async) => {
compile(tcb, `main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
.then((rtc) => {fixture = rtc})
.then((_) => rtr.config([
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
]))
.then((_) => rtr.navigateByUrl('/hello(modal)'))
.then((_) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
async.done();
});
}));

it('should navigate via the link DSL', inject([AsyncTestCompleter], (async) => {
compile(tcb, `main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
.then((rtc) => {fixture = rtc})
.then((_) => rtr.config([
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
]))
.then((_) => rtr.navigate(['/Hello', ['Modal']]))
.then((_) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
async.done();
});
}));
registerSpecs();

it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
compile(
tcb,
`<a [routerLink]="['/Hello', ['Modal']]">open modal</a> | main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
.then((rtc) => {fixture = rtc})
.then((_) => rtr.config([
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
]))
.then((_) => {
fixture.detectChanges();
expect(getHref(getLinkElement(fixture))).toEqual('/hello(modal)');
async.done();
});
}));

it('should navigate from a link click',
inject([AsyncTestCompleter, Location], (async, location) => {
compile(
tcb,
`<a [routerLink]="['/Hello', ['Modal']]">open modal</a> | main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
.then((rtc) => {fixture = rtc})
.then((_) => rtr.config([
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
]))
.then((_) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText('open modal | main {} | aux {}');

rtr.subscribe((_) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText('open modal | main {hello} | aux {modal}');
expect(location.urlChanges).toEqual(['/hello(modal)']);
async.done();
});

clickOnElement(getLinkElement(fixture));
});
}));
describeRouter('aux routes', () => {
itShouldRoute();
describeWith('a primary route', itShouldRoute);
});
}


@Component({selector: 'hello-cmp', template: `{{greeting}}`})
class HelloCmp {
greeting: string;
constructor() { this.greeting = 'hello'; }
}

@Component({selector: 'modal-cmp', template: `modal`})
class ModalCmp {
}

@Component({
selector: 'aux-cmp',
template: 'main {<router-outlet></router-outlet>} | ' +
'aux {<router-outlet name="modal"></router-outlet>}',
directives: [ROUTER_DIRECTIVES],
})
@RouteConfig([
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
])
class AuxCmp {
}
Loading

0 comments on commit 2a2f9a9

Please sign in to comment.