X Tutup
Skip to content

Commit 2a2f9a9

Browse files
committed
feat(router): support links with just auxiliary routes
Closes #5930
1 parent 909e70b commit 2a2f9a9

File tree

12 files changed

+466
-285
lines changed

12 files changed

+466
-285
lines changed

modules/angular1_router/lib/facades.es5

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ var ListWrapper = {
195195
return array[0];
196196
},
197197

198+
last: function(array) {
199+
return (array && array.length) > 0 ? array[array.length - 1] : null;
200+
},
201+
198202
map: function (l, fn) {
199203
return l.map(fn);
200204
},

modules/angular2/src/router/component_recognizer.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
AbstractRecognizer,
88
RouteRecognizer,
99
RedirectRecognizer,
10-
RouteMatch
10+
RouteMatch,
11+
PathMatch
1112
} from './route_recognizer';
1213
import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl';
1314
import {AsyncRouteHandler} from './async_route_handler';
@@ -117,6 +118,11 @@ export class ComponentRecognizer {
117118
}
118119
});
119120

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

modules/angular2/src/router/instruction.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ export abstract class Instruction {
111111
public child: Instruction;
112112
public auxInstruction: {[key: string]: Instruction} = {};
113113

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

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

118118
get specificity(): number {
119119
var total = 0;
@@ -181,7 +181,7 @@ export abstract class Instruction {
181181

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

187187
/** @internal */

modules/angular2/src/router/route_registry.ts

Lines changed: 154 additions & 120 deletions
Large diffs are not rendered by default.

modules/angular2/src/router/router.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ export class Router {
9595
throw new BaseException(`registerAuxOutlet expects to be called with an outlet with a name.`);
9696
}
9797

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

102100
this._auxRouters.set(outletName, router);
@@ -224,10 +222,12 @@ export class Router {
224222
/** @internal */
225223
_settleInstruction(instruction: Instruction): Promise<any> {
226224
return instruction.resolveComponent().then((_) => {
227-
instruction.component.reuse = false;
228-
229225
var unsettledInstructions: Array<Promise<any>> = [];
230226

227+
if (isPresent(instruction.component)) {
228+
instruction.component.reuse = false;
229+
}
230+
231231
if (isPresent(instruction.child)) {
232232
unsettledInstructions.push(this._settleInstruction(instruction.child));
233233
}
@@ -256,6 +256,9 @@ export class Router {
256256
if (isBlank(this._outlet)) {
257257
return _resolveToFalse;
258258
}
259+
if (isBlank(instruction.component)) {
260+
return _resolveToTrue;
261+
}
259262
return this._outlet.routerCanReuse(instruction.component)
260263
.then((result) => {
261264
instruction.component.reuse = result;
@@ -280,7 +283,7 @@ export class Router {
280283
if (isPresent(instruction)) {
281284
childInstruction = instruction.child;
282285
componentInstruction = instruction.component;
283-
reuse = instruction.component.reuse;
286+
reuse = isBlank(instruction.component) || instruction.component.reuse;
284287
}
285288
if (reuse) {
286289
next = _resolveToTrue;
@@ -304,8 +307,9 @@ export class Router {
304307
*/
305308
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
306309
this._currentInstruction = instruction;
310+
307311
var next: Promise<any> = _resolveToTrue;
308-
if (isPresent(this._outlet)) {
312+
if (isPresent(this._outlet) && isPresent(instruction.component)) {
309313
var componentInstruction = instruction.component;
310314
if (componentInstruction.reuse) {
311315
next = this._outlet.reuse(componentInstruction);
@@ -381,15 +385,12 @@ export class Router {
381385
}
382386

383387
private _getAncestorInstructions(): Instruction[] {
384-
var ancestorComponents = [];
388+
var ancestorInstructions = [this._currentInstruction];
385389
var ancestorRouter: Router = this;
386-
while (isPresent(ancestorRouter.parent) &&
387-
isPresent(ancestorRouter.parent._currentInstruction)) {
388-
ancestorRouter = ancestorRouter.parent;
389-
ancestorComponents.unshift(ancestorRouter._currentInstruction);
390+
while (isPresent(ancestorRouter = ancestorRouter.parent)) {
391+
ancestorInstructions.unshift(ancestorRouter._currentInstruction);
390392
}
391-
392-
return ancestorComponents;
393+
return ancestorInstructions;
393394
}
394395

395396

@@ -505,6 +506,9 @@ class ChildRouter extends Router {
505506
function canActivateOne(nextInstruction: Instruction,
506507
prevInstruction: Instruction): Promise<boolean> {
507508
var next = _resolveToTrue;
509+
if (isBlank(nextInstruction.component)) {
510+
return next;
511+
}
508512
if (isPresent(nextInstruction.child)) {
509513
next = canActivateOne(nextInstruction.child,
510514
isPresent(prevInstruction) ? prevInstruction.child : null);

modules/angular2/src/router/router_link.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,24 @@ export class RouterLink {
5353
// the instruction passed to the router to navigate
5454
private _navigationInstruction: Instruction;
5555

56-
constructor(private _router: Router, private _location: Location) {}
56+
constructor(private _router: Router, private _location: Location) {
57+
// we need to update the link whenever a route changes to account for aux routes
58+
this._router.subscribe((_) => this._updateLink());
59+
}
60+
61+
// because auxiliary links take existing primary and auxiliary routes into account,
62+
// we need to update the link whenever params or other routes change.
63+
private _updateLink(): void {
64+
this._navigationInstruction = this._router.generate(this._routeParams);
65+
var navigationHref = this._navigationInstruction.toLinkUrl();
66+
this.visibleHref = this._location.prepareExternalUrl(navigationHref);
67+
}
5768

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

6071
set routeParams(changes: any[]) {
6172
this._routeParams = changes;
62-
this._navigationInstruction = this._router.generate(this._routeParams);
63-
64-
var navigationHref = this._navigationInstruction.toLinkUrl();
65-
this.visibleHref = this._location.prepareExternalUrl(navigationHref);
73+
this._updateLink();
6674
}
6775

6876
onClick(): boolean {

modules/angular2/test/router/integration/async_route_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {registerSpecs} from './impl/async_route_spec_impl';
1212
export function main() {
1313
registerSpecs();
1414

15-
ddescribeRouter('async routes', () => {
15+
describeRouter('async routes', () => {
1616
describeWithout('children', () => {
1717
describeWith('route data', itShouldRoute);
1818
describeWithAndWithout('params', itShouldRoute);
Lines changed: 12 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,19 @@
11
import {
2-
ComponentFixture,
3-
AsyncTestCompleter,
4-
TestComponentBuilder,
5-
beforeEach,
6-
ddescribe,
7-
xdescribe,
8-
describe,
9-
el,
10-
expect,
11-
iit,
12-
inject,
13-
beforeEachProviders,
14-
it,
15-
xit
16-
} from 'angular2/testing_internal';
2+
describeRouter,
3+
ddescribeRouter,
4+
describeWith,
5+
describeWithout,
6+
describeWithAndWithout,
7+
itShouldRoute
8+
} from './util';
179

18-
import {provide, Component, Injector, Inject} from 'angular2/core';
19-
20-
import {Router, ROUTER_DIRECTIVES, RouteParams, RouteData, Location} from 'angular2/router';
21-
import {RouteConfig, Route, AuxRoute, Redirect} from 'angular2/src/router/route_config_decorator';
22-
23-
import {TEST_ROUTER_PROVIDERS, RootCmp, compile, clickOnElement, getHref} from './util';
24-
25-
function getLinkElement(rtc: ComponentFixture) {
26-
return rtc.debugElement.componentViewChildren[0].nativeElement;
27-
}
28-
29-
var cmpInstanceCount;
30-
var childCmpInstanceCount;
10+
import {registerSpecs} from './impl/aux_route_spec_impl';
3111

3212
export function main() {
33-
describe('auxiliary routes', () => {
34-
35-
var tcb: TestComponentBuilder;
36-
var fixture: ComponentFixture;
37-
var rtr;
38-
39-
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
40-
41-
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
42-
tcb = tcBuilder;
43-
rtr = router;
44-
childCmpInstanceCount = 0;
45-
cmpInstanceCount = 0;
46-
}));
47-
48-
it('should recognize and navigate from the URL', inject([AsyncTestCompleter], (async) => {
49-
compile(tcb, `main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
50-
.then((rtc) => {fixture = rtc})
51-
.then((_) => rtr.config([
52-
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
53-
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
54-
]))
55-
.then((_) => rtr.navigateByUrl('/hello(modal)'))
56-
.then((_) => {
57-
fixture.detectChanges();
58-
expect(fixture.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
59-
async.done();
60-
});
61-
}));
62-
63-
it('should navigate via the link DSL', inject([AsyncTestCompleter], (async) => {
64-
compile(tcb, `main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
65-
.then((rtc) => {fixture = rtc})
66-
.then((_) => rtr.config([
67-
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
68-
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
69-
]))
70-
.then((_) => rtr.navigate(['/Hello', ['Modal']]))
71-
.then((_) => {
72-
fixture.detectChanges();
73-
expect(fixture.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
74-
async.done();
75-
});
76-
}));
13+
registerSpecs();
7714

78-
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
79-
compile(
80-
tcb,
81-
`<a [routerLink]="['/Hello', ['Modal']]">open modal</a> | main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
82-
.then((rtc) => {fixture = rtc})
83-
.then((_) => rtr.config([
84-
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
85-
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
86-
]))
87-
.then((_) => {
88-
fixture.detectChanges();
89-
expect(getHref(getLinkElement(fixture))).toEqual('/hello(modal)');
90-
async.done();
91-
});
92-
}));
93-
94-
it('should navigate from a link click',
95-
inject([AsyncTestCompleter, Location], (async, location) => {
96-
compile(
97-
tcb,
98-
`<a [routerLink]="['/Hello', ['Modal']]">open modal</a> | main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
99-
.then((rtc) => {fixture = rtc})
100-
.then((_) => rtr.config([
101-
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
102-
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
103-
]))
104-
.then((_) => {
105-
fixture.detectChanges();
106-
expect(fixture.debugElement.nativeElement)
107-
.toHaveText('open modal | main {} | aux {}');
108-
109-
rtr.subscribe((_) => {
110-
fixture.detectChanges();
111-
expect(fixture.debugElement.nativeElement)
112-
.toHaveText('open modal | main {hello} | aux {modal}');
113-
expect(location.urlChanges).toEqual(['/hello(modal)']);
114-
async.done();
115-
});
116-
117-
clickOnElement(getLinkElement(fixture));
118-
});
119-
}));
15+
describeRouter('aux routes', () => {
16+
itShouldRoute();
17+
describeWith('a primary route', itShouldRoute);
12018
});
12119
}
122-
123-
124-
@Component({selector: 'hello-cmp', template: `{{greeting}}`})
125-
class HelloCmp {
126-
greeting: string;
127-
constructor() { this.greeting = 'hello'; }
128-
}
129-
130-
@Component({selector: 'modal-cmp', template: `modal`})
131-
class ModalCmp {
132-
}
133-
134-
@Component({
135-
selector: 'aux-cmp',
136-
template: 'main {<router-outlet></router-outlet>} | ' +
137-
'aux {<router-outlet name="modal"></router-outlet>}',
138-
directives: [ROUTER_DIRECTIVES],
139-
})
140-
@RouteConfig([
141-
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
142-
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
143-
])
144-
class AuxCmp {
145-
}

0 commit comments

Comments
 (0)
X Tutup