2015-11-23 16:26:47 -08:00
|
|
|
|
import {PathMatch} from './path_recognizer';
|
|
|
|
|
import {RouteRecognizer} from './route_recognizer';
|
|
|
|
|
import {Instruction, ComponentInstruction, PrimaryInstruction} from './instruction';
|
2015-11-06 17:34:07 -08:00
|
|
|
|
import {ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
|
|
|
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
2015-05-21 13:59:14 -07:00
|
|
|
|
import {
|
|
|
|
|
isPresent,
|
2015-10-30 18:08:18 -07:00
|
|
|
|
isArray,
|
2015-05-21 13:59:14 -07:00
|
|
|
|
isBlank,
|
|
|
|
|
isType,
|
2015-06-30 13:18:51 -07:00
|
|
|
|
isString,
|
2015-06-11 19:32:55 +02:00
|
|
|
|
isStringMap,
|
2015-05-21 13:59:14 -07:00
|
|
|
|
isFunction,
|
|
|
|
|
StringWrapper,
|
2015-08-10 13:05:08 -07:00
|
|
|
|
Type,
|
2015-07-13 15:29:14 -07:00
|
|
|
|
getTypeNameForDebugging
|
2015-11-06 17:34:07 -08:00
|
|
|
|
} from 'angular2/src/facade/lang';
|
|
|
|
|
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
2015-08-10 13:05:08 -07:00
|
|
|
|
import {
|
|
|
|
|
RouteConfig,
|
|
|
|
|
AsyncRoute,
|
|
|
|
|
Route,
|
|
|
|
|
AuxRoute,
|
|
|
|
|
Redirect,
|
|
|
|
|
RouteDefinition
|
|
|
|
|
} from './route_config_impl';
|
2015-11-23 16:26:47 -08:00
|
|
|
|
import {reflector} from 'angular2/src/core/reflection/reflection';
|
2015-10-11 13:17:06 +02:00
|
|
|
|
import {Injectable} from 'angular2/angular2';
|
2015-09-18 15:41:02 -07:00
|
|
|
|
import {normalizeRouteConfig, assertComponentExists} from './route_config_nomalizer';
|
2015-08-13 11:09:22 -07:00
|
|
|
|
import {parser, Url, pathSegmentsToUrl} from './url_parser';
|
2015-07-17 13:36:53 -07:00
|
|
|
|
|
|
|
|
|
var _resolveToNull = PromiseWrapper.resolve(null);
|
2015-04-17 09:59:56 -07:00
|
|
|
|
|
2015-05-15 02:05:57 -07:00
|
|
|
|
/**
|
|
|
|
|
* The RouteRegistry holds route configurations for each component in an Angular app.
|
2015-05-29 14:58:41 -07:00
|
|
|
|
* It is responsible for creating Instructions from URLs, and generating URLs based on route and
|
|
|
|
|
* parameters.
|
2015-05-15 02:05:57 -07:00
|
|
|
|
*/
|
2015-06-29 10:22:00 +02:00
|
|
|
|
@Injectable()
|
2015-04-17 09:59:56 -07:00
|
|
|
|
export class RouteRegistry {
|
2015-11-23 16:26:47 -08:00
|
|
|
|
private _rules = new Map<any, RouteRecognizer>();
|
2015-06-30 13:18:51 -07:00
|
|
|
|
|
2015-05-15 02:05:57 -07:00
|
|
|
|
/**
|
|
|
|
|
* Given a component and a configuration object, add the route to this registry
|
|
|
|
|
*/
|
2015-07-17 13:36:53 -07:00
|
|
|
|
config(parentComponent: any, config: RouteDefinition): void {
|
2015-11-23 16:26:47 -08:00
|
|
|
|
config = normalizeRouteConfig(config);
|
2015-05-01 15:50:12 -07:00
|
|
|
|
|
2015-08-10 13:05:08 -07:00
|
|
|
|
// this is here because Dart type guard reasons
|
|
|
|
|
if (config instanceof Route) {
|
|
|
|
|
assertComponentExists(config.component, config.path);
|
|
|
|
|
} else if (config instanceof AuxRoute) {
|
|
|
|
|
assertComponentExists(config.component, config.path);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
var recognizer: RouteRecognizer = this._rules.get(parentComponent);
|
2015-05-14 15:24:35 +02:00
|
|
|
|
|
|
|
|
|
if (isBlank(recognizer)) {
|
2015-11-23 16:26:47 -08:00
|
|
|
|
recognizer = new RouteRecognizer();
|
2015-06-17 16:21:40 -07:00
|
|
|
|
this._rules.set(parentComponent, recognizer);
|
2015-04-17 09:59:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-13 16:12:48 -07:00
|
|
|
|
var terminal = recognizer.config(config);
|
2015-05-21 13:59:14 -07:00
|
|
|
|
|
2015-07-13 16:12:48 -07:00
|
|
|
|
if (config instanceof Route) {
|
2015-06-17 11:57:38 -07:00
|
|
|
|
if (terminal) {
|
2015-07-13 16:12:48 -07:00
|
|
|
|
assertTerminalComponent(config.component, config.path);
|
2015-06-17 11:57:38 -07:00
|
|
|
|
} else {
|
2015-07-13 16:12:48 -07:00
|
|
|
|
this.configFromComponent(config.component);
|
2015-06-17 11:57:38 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-05-15 02:05:57 -07:00
|
|
|
|
/**
|
|
|
|
|
* Reads the annotations of a component and configures the registry based on them
|
|
|
|
|
*/
|
2015-07-17 13:36:53 -07:00
|
|
|
|
configFromComponent(component: any): void {
|
2015-04-17 09:59:56 -07:00
|
|
|
|
if (!isType(component)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't read the annotations from a type more than once –
|
|
|
|
|
// this prevents an infinite loop if a component routes recursively.
|
2015-06-17 21:42:56 -07:00
|
|
|
|
if (this._rules.has(component)) {
|
2015-04-17 09:59:56 -07:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var annotations = reflector.annotations(component);
|
|
|
|
|
if (isPresent(annotations)) {
|
2015-05-29 14:58:41 -07:00
|
|
|
|
for (var i = 0; i < annotations.length; i++) {
|
2015-04-17 09:59:56 -07:00
|
|
|
|
var annotation = annotations[i];
|
|
|
|
|
|
|
|
|
|
if (annotation instanceof RouteConfig) {
|
2015-10-07 09:09:43 -07:00
|
|
|
|
let routeCfgs: RouteDefinition[] = annotation.configs;
|
|
|
|
|
routeCfgs.forEach(config => this.config(component, config));
|
2015-04-17 09:59:56 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-05-15 02:05:57 -07:00
|
|
|
|
/**
|
|
|
|
|
* Given a URL and a parent component, return the most specific instruction for navigating
|
2015-07-01 16:34:37 -07:00
|
|
|
|
* the application into the state specified by the url
|
2015-05-15 02:05:57 -07:00
|
|
|
|
*/
|
2015-11-23 16:26:47 -08:00
|
|
|
|
recognize(url: string, parentComponent: any): Promise<Instruction> {
|
2015-07-17 13:36:53 -07:00
|
|
|
|
var parsedUrl = parser.parse(url);
|
2015-11-23 16:26:47 -08:00
|
|
|
|
return this._recognize(parsedUrl, parentComponent);
|
2015-07-17 13:36:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
private _recognize(parsedUrl: Url, parentComponent): Promise<Instruction> {
|
|
|
|
|
return this._recognizePrimaryRoute(parsedUrl, parentComponent)
|
|
|
|
|
.then((instruction: PrimaryInstruction) =>
|
|
|
|
|
this._completeAuxiliaryRouteMatches(instruction, parentComponent));
|
|
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
private _recognizePrimaryRoute(parsedUrl: Url, parentComponent): Promise<PrimaryInstruction> {
|
2015-06-17 16:21:40 -07:00
|
|
|
|
var componentRecognizer = this._rules.get(parentComponent);
|
2015-04-17 09:59:56 -07:00
|
|
|
|
if (isBlank(componentRecognizer)) {
|
2015-09-14 11:22:54 -07:00
|
|
|
|
return _resolveToNull;
|
2015-04-17 09:59:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-05-15 02:05:57 -07:00
|
|
|
|
// Matches some beginning part of the given URL
|
2015-11-23 16:26:47 -08:00
|
|
|
|
var possibleMatches = componentRecognizer.recognize(parsedUrl);
|
2015-05-15 02:05:57 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
var matchPromises =
|
|
|
|
|
possibleMatches.map(candidate => this._completePrimaryRouteMatch(candidate));
|
2015-05-21 13:59:14 -07:00
|
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
|
return PromiseWrapper.all(matchPromises).then(mostSpecific);
|
2015-05-21 13:59:14 -07:00
|
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
private _completePrimaryRouteMatch(partialMatch: PathMatch): Promise<PrimaryInstruction> {
|
|
|
|
|
var instruction = partialMatch.instruction;
|
|
|
|
|
return instruction.resolveComponentType().then((componentType) => {
|
|
|
|
|
this.configFromComponent(componentType);
|
2015-05-12 14:53:13 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
if (instruction.terminal) {
|
|
|
|
|
return new PrimaryInstruction(instruction, null, partialMatch.remainingAux);
|
|
|
|
|
}
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5352
2015-11-02 16:14:10 -08:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
return this._recognizePrimaryRoute(partialMatch.remaining, componentType)
|
|
|
|
|
.then((childInstruction) => {
|
|
|
|
|
if (isBlank(childInstruction)) {
|
|
|
|
|
return null;
|
|
|
|
|
} else {
|
|
|
|
|
return new PrimaryInstruction(instruction, childInstruction,
|
|
|
|
|
partialMatch.remainingAux);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2015-04-17 09:59:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
private _completeAuxiliaryRouteMatches(instruction: PrimaryInstruction,
|
|
|
|
|
parentComponent: any): Promise<Instruction> {
|
|
|
|
|
if (isBlank(instruction)) {
|
|
|
|
|
return _resolveToNull;
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5352
2015-11-02 16:14:10 -08:00
|
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
var componentRecognizer = this._rules.get(parentComponent);
|
|
|
|
|
var auxInstructions: {[key: string]: Instruction} = {};
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5352
2015-11-02 16:14:10 -08:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
var promises = instruction.auxUrls.map((auxSegment: Url) => {
|
|
|
|
|
var match = componentRecognizer.recognizeAuxiliary(auxSegment);
|
|
|
|
|
if (isBlank(match)) {
|
|
|
|
|
return _resolveToNull;
|
|
|
|
|
}
|
|
|
|
|
return this._completePrimaryRouteMatch(match).then((auxInstruction: PrimaryInstruction) => {
|
|
|
|
|
if (isPresent(auxInstruction)) {
|
|
|
|
|
return this._completeAuxiliaryRouteMatches(auxInstruction, parentComponent)
|
|
|
|
|
.then((finishedAuxRoute: Instruction) => {
|
|
|
|
|
auxInstructions[auxSegment.path] = finishedAuxRoute;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return PromiseWrapper.all(promises).then((_) => {
|
|
|
|
|
if (isBlank(instruction.child)) {
|
|
|
|
|
return new Instruction(instruction.component, null, auxInstructions);
|
|
|
|
|
}
|
|
|
|
|
return this._completeAuxiliaryRouteMatches(instruction.child,
|
|
|
|
|
instruction.component.componentType)
|
|
|
|
|
.then((completeChild) => {
|
|
|
|
|
return new Instruction(instruction.component, completeChild, auxInstructions);
|
|
|
|
|
});
|
|
|
|
|
});
|
2015-07-17 13:36:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
/**
|
|
|
|
|
* Given a normalized list with component names and params like: `['user', {id: 3 }]`
|
|
|
|
|
* generates a url with a leading slash relative to the provided `parentComponent`.
|
2015-06-30 13:18:51 -07:00
|
|
|
|
*/
|
2015-11-23 16:26:47 -08:00
|
|
|
|
generate(linkParams: any[], parentComponent: any, _aux = false): Instruction {
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5352
2015-11-02 16:14:10 -08:00
|
|
|
|
let linkIndex = 0;
|
|
|
|
|
let routeName = linkParams[linkIndex];
|
2015-07-17 13:36:53 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
// TODO: this is kind of odd but it makes existing assertions pass
|
|
|
|
|
if (isBlank(parentComponent)) {
|
|
|
|
|
throw new BaseException(`Could not find route named "${routeName}".`);
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-30 18:08:18 -07:00
|
|
|
|
if (!isString(routeName)) {
|
|
|
|
|
throw new BaseException(`Unexpected segment "${routeName}" in link DSL. Expected a string.`);
|
|
|
|
|
} else if (routeName == '' || routeName == '.' || routeName == '..') {
|
|
|
|
|
throw new BaseException(`"${routeName}/" is only allowed at the beginning of a link DSL.`);
|
2015-06-30 13:18:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-30 18:08:18 -07:00
|
|
|
|
let params = {};
|
|
|
|
|
if (linkIndex + 1 < linkParams.length) {
|
|
|
|
|
let nextSegment = linkParams[linkIndex + 1];
|
|
|
|
|
if (isStringMap(nextSegment) && !isArray(nextSegment)) {
|
|
|
|
|
params = nextSegment;
|
|
|
|
|
linkIndex += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-09-09 07:49:02 -07:00
|
|
|
|
|
2015-10-30 18:08:18 -07:00
|
|
|
|
let auxInstructions: {[key: string]: Instruction} = {};
|
|
|
|
|
var nextSegment;
|
|
|
|
|
while (linkIndex + 1 < linkParams.length && isArray(nextSegment = linkParams[linkIndex + 1])) {
|
2015-11-23 16:26:47 -08:00
|
|
|
|
auxInstructions[nextSegment[0]] = this.generate(nextSegment, parentComponent, true);
|
2015-10-30 18:08:18 -07:00
|
|
|
|
linkIndex += 1;
|
|
|
|
|
}
|
2015-09-09 07:49:02 -07:00
|
|
|
|
|
2015-10-30 18:08:18 -07:00
|
|
|
|
var componentRecognizer = this._rules.get(parentComponent);
|
|
|
|
|
if (isBlank(componentRecognizer)) {
|
|
|
|
|
throw new BaseException(
|
|
|
|
|
`Component "${getTypeNameForDebugging(parentComponent)}" has no route config.`);
|
2015-09-09 07:49:02 -07:00
|
|
|
|
}
|
2015-08-13 11:09:22 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
var componentInstruction = _aux ? componentRecognizer.generateAuxiliary(routeName, params) :
|
|
|
|
|
componentRecognizer.generate(routeName, params);
|
2015-10-30 18:08:18 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
if (isBlank(componentInstruction)) {
|
2015-10-30 18:08:18 -07:00
|
|
|
|
throw new BaseException(
|
|
|
|
|
`Component "${getTypeNameForDebugging(parentComponent)}" has no route named "${routeName}".`);
|
|
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
var childInstruction = null;
|
|
|
|
|
if (linkIndex + 1 < linkParams.length) {
|
|
|
|
|
var remaining = linkParams.slice(linkIndex + 1);
|
|
|
|
|
childInstruction = this.generate(remaining, componentInstruction.componentType);
|
|
|
|
|
} else if (!componentInstruction.terminal) {
|
|
|
|
|
throw new BaseException(
|
|
|
|
|
`Link "${ListWrapper.toJSON(linkParams)}" does not resolve to a terminal or async instruction.`);
|
2015-07-17 13:36:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
return new Instruction(componentInstruction, childInstruction, auxInstructions);
|
2015-04-17 09:59:56 -07:00
|
|
|
|
}
|
2015-08-13 11:09:22 -07:00
|
|
|
|
|
2015-10-26 17:18:08 +00:00
|
|
|
|
public hasRoute(name: string, parentComponent: any): boolean {
|
2015-11-23 16:26:47 -08:00
|
|
|
|
var componentRecognizer: RouteRecognizer = this._rules.get(parentComponent);
|
2015-10-26 17:18:08 +00:00
|
|
|
|
if (isBlank(componentRecognizer)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return componentRecognizer.hasRoute(name);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
// if the child includes a redirect like : "/" -> "/something",
|
|
|
|
|
// we want to honor that redirection when creating the link
|
|
|
|
|
private _generateRedirects(componentCursor: Type): Instruction {
|
2015-08-13 11:09:22 -07:00
|
|
|
|
if (isBlank(componentCursor)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
var componentRecognizer = this._rules.get(componentCursor);
|
2015-11-23 16:26:47 -08:00
|
|
|
|
if (isBlank(componentRecognizer)) {
|
2015-08-13 11:09:22 -07:00
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
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);
|
2015-08-13 11:09:22 -07:00
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
if (isPresent(primaryInstruction)) {
|
|
|
|
|
var child = this._generateRedirects(primaryInstruction.instruction.componentType);
|
|
|
|
|
return new Instruction(primaryInstruction.instruction, child, {});
|
|
|
|
|
}
|
|
|
|
|
return null;
|
2015-08-13 11:09:22 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-23 16:26:47 -08:00
|
|
|
|
return null;
|
2015-08-13 11:09:22 -07:00
|
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-05-21 13:59:14 -07:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Given a list of instructions, returns the most specific instruction
|
|
|
|
|
*/
|
2015-11-23 16:26:47 -08:00
|
|
|
|
function mostSpecific(instructions: PrimaryInstruction[]): PrimaryInstruction {
|
|
|
|
|
return ListWrapper.maximum(
|
|
|
|
|
instructions, (instruction: PrimaryInstruction) => instruction.component.specificity);
|
2015-04-29 15:47:12 -07:00
|
|
|
|
}
|
2015-06-17 11:57:38 -07:00
|
|
|
|
|
|
|
|
|
function assertTerminalComponent(component, path) {
|
|
|
|
|
if (!isType(component)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var annotations = reflector.annotations(component);
|
|
|
|
|
if (isPresent(annotations)) {
|
|
|
|
|
for (var i = 0; i < annotations.length; i++) {
|
|
|
|
|
var annotation = annotations[i];
|
|
|
|
|
|
|
|
|
|
if (annotation instanceof RouteConfig) {
|
|
|
|
|
throw new BaseException(
|
|
|
|
|
`Child routes are not allowed for "${path}". Use "..." on the parent's route path.`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|