fix(router): recognize child components with empty segments

Previosly, recognition ended when a parent captured all the parsed URL segments.
This caused routes that delegated from a parent to a child with an empty segment
to never be recognized.

Closes #4178
This commit is contained in:
Brian Ford 2015-09-14 11:22:54 -07:00
parent 63e785902f
commit 309944931f
3 changed files with 38 additions and 29 deletions

View File

@ -20,7 +20,7 @@ import {RouteHandler} from './route_handler';
import {Url, RootUrl, serializeParams} from './url_parser'; import {Url, RootUrl, serializeParams} from './url_parser';
import {ComponentInstruction} from './instruction'; import {ComponentInstruction} from './instruction';
export class TouchMap { class TouchMap {
map: StringMap<string, string> = {}; map: StringMap<string, string> = {};
keys: StringMap<string, boolean> = {}; keys: StringMap<string, boolean> = {};
@ -54,7 +54,7 @@ function normalizeString(obj: any): string {
} }
} }
export interface Segment { interface Segment {
name: string; name: string;
generate(params: TouchMap): string; generate(params: TouchMap): string;
match(path: string): boolean; match(path: string): boolean;
@ -75,7 +75,7 @@ class StaticSegment implements Segment {
class DynamicSegment implements Segment { class DynamicSegment implements Segment {
constructor(public name: string) {} constructor(public name: string) {}
match(path: string): boolean { return true; } match(path: string): boolean { return path.length > 0; }
generate(params: TouchMap): string { generate(params: TouchMap): string {
if (!StringMapWrapper.contains(params.map, this.name)) { if (!StringMapWrapper.contains(params.map, this.name)) {
throw new BaseException( throw new BaseException(
@ -224,10 +224,7 @@ export class PathRecognizer {
break; break;
} }
if (isBlank(currentSegment)) { if (isPresent(currentSegment)) {
return null;
}
captured.push(currentSegment.path); captured.push(currentSegment.path);
// the star segment consumes all of the remaining URL, including matrix params // the star segment consumes all of the remaining URL, including matrix params
@ -244,6 +241,9 @@ export class PathRecognizer {
} }
nextSegment = currentSegment.child; nextSegment = currentSegment.child;
} else if (!segment.match('')) {
return null;
}
} }
if (this.terminal && isPresent(nextSegment)) { if (this.terminal && isPresent(nextSegment)) {

View File

@ -120,7 +120,7 @@ export class RouteRegistry {
private _recognizePrimaryRoute(parsedUrl: Url, parentComponent): Promise<PrimaryInstruction> { private _recognizePrimaryRoute(parsedUrl: Url, parentComponent): Promise<PrimaryInstruction> {
var componentRecognizer = this._rules.get(parentComponent); var componentRecognizer = this._rules.get(parentComponent);
if (isBlank(componentRecognizer)) { if (isBlank(componentRecognizer)) {
return PromiseWrapper.resolve(null); return _resolveToNull;
} }
// Matches some beginning part of the given URL // Matches some beginning part of the given URL
@ -137,12 +137,8 @@ export class RouteRegistry {
return instruction.resolveComponentType().then((componentType) => { return instruction.resolveComponentType().then((componentType) => {
this.configFromComponent(componentType); this.configFromComponent(componentType);
if (isBlank(partialMatch.remaining)) {
if (instruction.terminal) { if (instruction.terminal) {
return new PrimaryInstruction(instruction, null, partialMatch.remainingAux); return new PrimaryInstruction(instruction, null, partialMatch.remainingAux);
} else {
return null;
}
} }
return this._recognizePrimaryRoute(partialMatch.remaining, componentType) return this._recognizePrimaryRoute(partialMatch.remaining, componentType)

View File

@ -115,6 +115,18 @@ export function main() {
}); });
})); }));
it('should navigate to child routes that capture an empty path',
inject([AsyncTestCompleter], (async) => {
compile('outer { <router-outlet></router-outlet> }')
.then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
.then((_) => rtr.navigateByUrl('/a'))
.then((_) => {
rootTC.detectChanges();
expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
async.done();
});
}));
it('should navigate to child routes of async routes', inject([AsyncTestCompleter], (async) => { it('should navigate to child routes of async routes', inject([AsyncTestCompleter], (async) => {
compile('outer { <router-outlet></router-outlet> }') compile('outer { <router-outlet></router-outlet> }')
@ -309,7 +321,8 @@ function parentLoader() {
@Component({selector: 'parent-cmp'}) @Component({selector: 'parent-cmp'})
@View({template: "inner { <router-outlet></router-outlet> }", directives: [RouterOutlet]}) @View({template: "inner { <router-outlet></router-outlet> }", directives: [RouterOutlet]})
@RouteConfig([new Route({path: '/b', component: HelloCmp})]) @RouteConfig(
[new Route({path: '/b', component: HelloCmp}), new Route({path: '/', component: HelloCmp})])
class ParentCmp { class ParentCmp {
constructor() {} constructor() {}
} }