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:
parent
63e785902f
commit
309944931f
|
@ -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,26 +224,26 @@ export class PathRecognizer {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBlank(currentSegment)) {
|
if (isPresent(currentSegment)) {
|
||||||
|
captured.push(currentSegment.path);
|
||||||
|
|
||||||
|
// the star segment consumes all of the remaining URL, including matrix params
|
||||||
|
if (segment instanceof StarSegment) {
|
||||||
|
positionalParams[segment.name] = currentSegment.toString();
|
||||||
|
nextSegment = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment instanceof DynamicSegment) {
|
||||||
|
positionalParams[segment.name] = currentSegment.path;
|
||||||
|
} else if (!segment.match(currentSegment.path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSegment = currentSegment.child;
|
||||||
|
} else if (!segment.match('')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
captured.push(currentSegment.path);
|
|
||||||
|
|
||||||
// the star segment consumes all of the remaining URL, including matrix params
|
|
||||||
if (segment instanceof StarSegment) {
|
|
||||||
positionalParams[segment.name] = currentSegment.toString();
|
|
||||||
nextSegment = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (segment instanceof DynamicSegment) {
|
|
||||||
positionalParams[segment.name] = currentSegment.path;
|
|
||||||
} else if (!segment.match(currentSegment.path)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextSegment = currentSegment.child;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.terminal && isPresent(nextSegment)) {
|
if (this.terminal && isPresent(nextSegment)) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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() {}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue