fix(router): allow router-link to link to redirects
Closes #3335 Closes #3624
This commit is contained in:
parent
b5c4d8ba79
commit
72e0b8f7dc
|
@ -216,17 +216,17 @@ export class PathRecognizer {
|
||||||
var captured = [];
|
var captured = [];
|
||||||
|
|
||||||
for (var i = 0; i < this._segments.length; i += 1) {
|
for (var i = 0; i < this._segments.length; i += 1) {
|
||||||
if (isBlank(nextSegment)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
currentSegment = nextSegment;
|
|
||||||
|
|
||||||
var segment = this._segments[i];
|
var segment = this._segments[i];
|
||||||
|
|
||||||
|
currentSegment = nextSegment;
|
||||||
if (segment instanceof ContinuationSegment) {
|
if (segment instanceof ContinuationSegment) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isBlank(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
|
||||||
|
@ -251,18 +251,26 @@ export class PathRecognizer {
|
||||||
|
|
||||||
var urlPath = captured.join('/');
|
var urlPath = captured.join('/');
|
||||||
|
|
||||||
|
var auxiliary;
|
||||||
|
var instruction: ComponentInstruction;
|
||||||
|
if (isPresent(currentSegment)) {
|
||||||
// If this is the root component, read query params. Otherwise, read matrix params.
|
// If this is the root component, read query params. Otherwise, read matrix params.
|
||||||
var paramsSegment = beginningSegment instanceof RootUrl ? beginningSegment : currentSegment;
|
var paramsSegment = beginningSegment instanceof RootUrl ? beginningSegment : currentSegment;
|
||||||
|
|
||||||
|
|
||||||
var allParams = isPresent(paramsSegment.params) ?
|
var allParams = isPresent(paramsSegment.params) ?
|
||||||
StringMapWrapper.merge(paramsSegment.params, positionalParams) :
|
StringMapWrapper.merge(paramsSegment.params, positionalParams) :
|
||||||
positionalParams;
|
positionalParams;
|
||||||
|
|
||||||
var urlParams = serializeParams(paramsSegment.params);
|
var urlParams = serializeParams(paramsSegment.params);
|
||||||
|
|
||||||
var instruction = new ComponentInstruction(urlPath, urlParams, this, allParams);
|
instruction = new ComponentInstruction(urlPath, urlParams, this, allParams);
|
||||||
|
|
||||||
return new PathMatch(instruction, nextSegment, currentSegment.auxiliary);
|
auxiliary = currentSegment.auxiliary;
|
||||||
|
} else {
|
||||||
|
instruction = new ComponentInstruction(urlPath, [], this, positionalParams);
|
||||||
|
auxiliary = [];
|
||||||
|
}
|
||||||
|
return new PathMatch(instruction, nextSegment, auxiliary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ import {
|
||||||
import {reflector} from 'angular2/src/reflection/reflection';
|
import {reflector} from 'angular2/src/reflection/reflection';
|
||||||
import {Injectable} from 'angular2/di';
|
import {Injectable} from 'angular2/di';
|
||||||
import {normalizeRouteConfig} from './route_config_nomalizer';
|
import {normalizeRouteConfig} from './route_config_nomalizer';
|
||||||
import {parser, Url} from './url_parser';
|
import {parser, Url, pathSegmentsToUrl} from './url_parser';
|
||||||
|
|
||||||
var _resolveToNull = PromiseWrapper.resolve(null);
|
var _resolveToNull = PromiseWrapper.resolve(null);
|
||||||
|
|
||||||
|
@ -236,7 +236,8 @@ export class RouteRegistry {
|
||||||
componentCursor = response.componentType;
|
componentCursor = response.componentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
var instruction = null;
|
var instruction: Instruction = this._generateRedirects(componentCursor);
|
||||||
|
|
||||||
|
|
||||||
while (segments.length > 0) {
|
while (segments.length > 0) {
|
||||||
instruction = new Instruction(segments.pop(), instruction, {});
|
instruction = new Instruction(segments.pop(), instruction, {});
|
||||||
|
@ -244,6 +245,38 @@ export class RouteRegistry {
|
||||||
|
|
||||||
return instruction;
|
return instruction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the child includes a redirect like : "/" -> "/something",
|
||||||
|
// we want to honor that redirection when creating the link
|
||||||
|
private _generateRedirects(componentCursor: Type): Instruction {
|
||||||
|
if (isBlank(componentCursor)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var componentRecognizer = this._rules.get(componentCursor);
|
||||||
|
if (isBlank(componentRecognizer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < componentRecognizer.redirects.length; i += 1) {
|
||||||
|
let redirect = componentRecognizer.redirects[i];
|
||||||
|
|
||||||
|
// we only handle redirecting from an empty segment
|
||||||
|
if (redirect.segments.length == 1 && redirect.segments[0] == '') {
|
||||||
|
var toSegments = pathSegmentsToUrl(redirect.toSegments);
|
||||||
|
var matches = componentRecognizer.recognize(toSegments);
|
||||||
|
var primaryInstruction =
|
||||||
|
ListWrapper.maximum(matches, (match: PathMatch) => match.instruction.specificity);
|
||||||
|
|
||||||
|
if (isPresent(primaryInstruction)) {
|
||||||
|
var child = this._generateRedirects(primaryInstruction.instruction.componentType);
|
||||||
|
return new Instruction(primaryInstruction.instruction, child, {});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -251,20 +284,8 @@ export class RouteRegistry {
|
||||||
* Given a list of instructions, returns the most specific instruction
|
* Given a list of instructions, returns the most specific instruction
|
||||||
*/
|
*/
|
||||||
function mostSpecific(instructions: List<PrimaryInstruction>): PrimaryInstruction {
|
function mostSpecific(instructions: List<PrimaryInstruction>): PrimaryInstruction {
|
||||||
if (instructions.length == 0) {
|
return ListWrapper.maximum(
|
||||||
return null;
|
instructions, (instruction: PrimaryInstruction) => instruction.component.specificity);
|
||||||
}
|
|
||||||
var mostSpecificSolution = instructions[0];
|
|
||||||
for (var solutionIndex = 1; solutionIndex < instructions.length; solutionIndex++) {
|
|
||||||
var solution: PrimaryInstruction = instructions[solutionIndex];
|
|
||||||
if (isBlank(solution)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (solution.component.specificity > mostSpecificSolution.component.specificity) {
|
|
||||||
mostSpecificSolution = solution;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mostSpecificSolution;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertTerminalComponent(component, path) {
|
function assertTerminalComponent(component, path) {
|
||||||
|
|
|
@ -57,6 +57,14 @@ export class RootUrl extends Url {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pathSegmentsToUrl(pathSegments: List<string>): Url {
|
||||||
|
var url = new Url(pathSegments[pathSegments.length - 1]);
|
||||||
|
for (var i = pathSegments.length - 2; i >= 0; i -= 1) {
|
||||||
|
url = new Url(pathSegments[i], url);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
var SEGMENT_RE = RegExpWrapper.create('^[^\\/\\(\\)\\?;=&]+');
|
var SEGMENT_RE = RegExpWrapper.create('^[^\\/\\(\\)\\?;=&]+');
|
||||||
function matchUrlSegment(str: string): string {
|
function matchUrlSegment(str: string): string {
|
||||||
var match = RegExpWrapper.firstMatch(SEGMENT_RE, str);
|
var match = RegExpWrapper.firstMatch(SEGMENT_RE, str);
|
||||||
|
|
|
@ -14,7 +14,13 @@ import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
import {Type} from 'angular2/src/facade/lang';
|
import {Type} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||||
import {RouteConfig, Route, AuxRoute, AsyncRoute} from 'angular2/src/router/route_config_decorator';
|
import {
|
||||||
|
RouteConfig,
|
||||||
|
Route,
|
||||||
|
Redirect,
|
||||||
|
AuxRoute,
|
||||||
|
AsyncRoute
|
||||||
|
} from 'angular2/src/router/route_config_decorator';
|
||||||
import {stringifyInstruction} from 'angular2/src/router/instruction';
|
import {stringifyInstruction} from 'angular2/src/router/instruction';
|
||||||
import {IS_DART} from '../platform';
|
import {IS_DART} from '../platform';
|
||||||
|
|
||||||
|
@ -45,6 +51,24 @@ export function main() {
|
||||||
.toEqual('second');
|
.toEqual('second');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate URLs that account for redirects', () => {
|
||||||
|
registry.config(
|
||||||
|
RootHostCmp,
|
||||||
|
new Route({path: '/first/...', component: DummyParentRedirectCmp, as: 'firstCmp'}));
|
||||||
|
|
||||||
|
expect(stringifyInstruction(registry.generate(['firstCmp'], RootHostCmp)))
|
||||||
|
.toEqual('first/second');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate URLs in a hierarchy of redirects', () => {
|
||||||
|
registry.config(
|
||||||
|
RootHostCmp,
|
||||||
|
new Route({path: '/first/...', component: DummyMultipleRedirectCmp, as: 'firstCmp'}));
|
||||||
|
|
||||||
|
expect(stringifyInstruction(registry.generate(['firstCmp'], RootHostCmp)))
|
||||||
|
.toEqual('first/second/third');
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate URLs with params', () => {
|
it('should generate URLs with params', () => {
|
||||||
registry.config(
|
registry.config(
|
||||||
RootHostCmp,
|
RootHostCmp,
|
||||||
|
@ -255,6 +279,28 @@ class DummyAsyncCmp {
|
||||||
class DummyCmpA {}
|
class DummyCmpA {}
|
||||||
class DummyCmpB {}
|
class DummyCmpB {}
|
||||||
|
|
||||||
|
@RouteConfig([
|
||||||
|
new Redirect({path: '/', redirectTo: '/third'}),
|
||||||
|
new Route({path: '/third', component: DummyCmpB, as: 'thirdCmp'})
|
||||||
|
])
|
||||||
|
class DummyRedirectCmp {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@RouteConfig([
|
||||||
|
new Redirect({path: '/', redirectTo: '/second'}),
|
||||||
|
new Route({path: '/second/...', component: DummyRedirectCmp, as: 'secondCmp'})
|
||||||
|
])
|
||||||
|
class DummyMultipleRedirectCmp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@RouteConfig([
|
||||||
|
new Redirect({path: '/', redirectTo: '/second'}),
|
||||||
|
new Route({path: '/second', component: DummyCmpB, as: 'secondCmp'})
|
||||||
|
])
|
||||||
|
class DummyParentRedirectCmp {
|
||||||
|
}
|
||||||
|
|
||||||
@RouteConfig([new Route({path: '/second', component: DummyCmpB, as: 'secondCmp'})])
|
@RouteConfig([new Route({path: '/second', component: DummyCmpB, as: 'secondCmp'})])
|
||||||
class DummyParentCmp {
|
class DummyParentCmp {
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue