2015-11-23 16:26:47 -08:00
import {
RegExp ,
RegExpWrapper ,
isBlank ,
isPresent ,
isType ,
isStringMap ,
Type
} from 'angular2/src/facade/lang' ;
import { BaseException , WrappedException } from 'angular2/src/facade/exceptions' ;
import { Map , MapWrapper , ListWrapper , StringMapWrapper } from 'angular2/src/facade/collection' ;
import { PathRecognizer , PathMatch } from './path_recognizer' ;
import { Route , AsyncRoute , AuxRoute , Redirect , RouteDefinition } from './route_config_impl' ;
import { AsyncRouteHandler } from './async_route_handler' ;
import { SyncRouteHandler } from './sync_route_handler' ;
2015-07-17 13:36:53 -07:00
import { Url } from './url_parser' ;
import { ComponentInstruction } from './instruction' ;
2015-04-17 09:59:56 -07:00
2015-11-23 16:26:47 -08:00
/ * *
* ` RouteRecognizer ` is responsible for recognizing routes for a single component .
* It is consumed by ` RouteRegistry ` , which knows how to recognize an entire hierarchy of
* components .
* /
export class RouteRecognizer {
names = new Map < string , PathRecognizer > ( ) ;
2015-07-21 01:26:43 -07:00
2015-11-23 16:26:47 -08:00
auxRoutes = new Map < string , PathRecognizer > ( ) ;
2015-09-14 16:01:09 -07:00
2015-11-23 16:26:47 -08:00
// TODO: optimize this into a trie
matchers : PathRecognizer [ ] = [ ] ;
2015-10-30 18:08:18 -07:00
2015-11-23 16:26:47 -08:00
// TODO: optimize this into a trie
redirects : Redirector [ ] = [ ] ;
2015-07-17 13:36:53 -07:00
2015-11-23 16:26:47 -08:00
config ( config : RouteDefinition ) : boolean {
var handler ;
if ( isPresent ( config . name ) && config . name [ 0 ] . toUpperCase ( ) != config . name [ 0 ] ) {
var suggestedName = config . name [ 0 ] . toUpperCase ( ) + config . name . substring ( 1 ) ;
throw new BaseException (
` Route " ${ config . path } " with name " ${ config . name } " does not begin with an uppercase letter. Route names should be CamelCase like " ${ suggestedName } ". ` ) ;
}
if ( config instanceof AuxRoute ) {
handler = new SyncRouteHandler ( config . component , config . data ) ;
let path = config . path . startsWith ( '/' ) ? config . path . substring ( 1 ) : config . path ;
var recognizer = new PathRecognizer ( config . path , handler ) ;
this . auxRoutes . set ( path , recognizer ) ;
return recognizer . terminal ;
}
if ( config instanceof Redirect ) {
this . redirects . push ( new Redirector ( config . path , config . redirectTo ) ) ;
return true ;
}
2015-07-17 13:36:53 -07:00
2015-11-23 16:26:47 -08:00
if ( config instanceof Route ) {
handler = new SyncRouteHandler ( config . component , config . data ) ;
} else if ( config instanceof AsyncRoute ) {
handler = new AsyncRouteHandler ( config . loader , config . data ) ;
}
var recognizer = new PathRecognizer ( config . path , handler ) ;
2015-07-17 13:36:53 -07:00
2015-11-23 16:26:47 -08:00
this . matchers . forEach ( ( matcher ) = > {
if ( recognizer . hash == matcher . hash ) {
throw new BaseException (
` Configuration ' ${ config . path } ' conflicts with existing route ' ${ matcher . path } ' ` ) ;
}
} ) ;
this . matchers . push ( recognizer ) ;
if ( isPresent ( config . name ) ) {
this . names . set ( config . name , recognizer ) ;
}
return recognizer . terminal ;
2015-04-17 09:59:56 -07:00
}
2015-11-23 16:26:47 -08:00
2015-05-15 02:05:57 -07:00
/ * *
2015-11-23 16:26:47 -08:00
* Given a URL , returns a list of ` RouteMatch ` es , which are partial recognitions for some route .
*
2015-05-15 02:05:57 -07:00
* /
2015-11-23 16:26:47 -08:00
recognize ( urlParse : Url ) : PathMatch [ ] {
var solutions = [ ] ;
2015-05-15 02:05:57 -07:00
2015-11-23 16:26:47 -08:00
urlParse = this . _redirect ( urlParse ) ;
2015-04-17 09:59:56 -07:00
2015-11-23 16:26:47 -08:00
this . matchers . forEach ( ( pathRecognizer : PathRecognizer ) = > {
var pathMatch = pathRecognizer . recognize ( urlParse ) ;
2015-07-21 01:26:43 -07:00
2015-11-23 16:26:47 -08:00
if ( isPresent ( pathMatch ) ) {
solutions . push ( pathMatch ) ;
}
} ) ;
2015-04-17 09:59:56 -07:00
2015-11-23 16:26:47 -08:00
return solutions ;
}
2015-04-17 09:59:56 -07:00
2015-11-23 16:26:47 -08:00
/** @internal */
_redirect ( urlParse : Url ) : Url {
for ( var i = 0 ; i < this . redirects . length ; i += 1 ) {
let redirector = this . redirects [ i ] ;
var redirectedUrl = redirector . redirect ( urlParse ) ;
if ( isPresent ( redirectedUrl ) ) {
return redirectedUrl ;
}
}
2015-07-17 13:36:53 -07:00
2015-11-23 16:26:47 -08:00
return urlParse ;
2015-07-17 13:36:53 -07:00
}
2015-11-23 16:26:47 -08:00
recognizeAuxiliary ( urlParse : Url ) : PathMatch {
var pathRecognizer = this . auxRoutes . get ( urlParse . path ) ;
if ( isBlank ( pathRecognizer ) ) {
2015-07-17 13:36:53 -07:00
return null ;
}
2015-11-23 16:26:47 -08:00
return pathRecognizer . recognize ( urlParse ) ;
2015-10-30 18:08:18 -07:00
}
2015-11-23 16:26:47 -08:00
hasRoute ( name : string ) : boolean { return this . names . has ( name ) ; }
generate ( name : string , params : any ) : ComponentInstruction {
var pathRecognizer : PathRecognizer = this . names . get ( name ) ;
if ( isBlank ( pathRecognizer ) ) {
return null ;
}
return pathRecognizer . generate ( params ) ;
2015-07-21 01:26:43 -07:00
}
2015-11-23 16:26:47 -08:00
}
export class Redirector {
segments : string [ ] = [ ] ;
toSegments : string [ ] = [ ] ;
2015-06-29 10:37:55 +02:00
2015-11-23 16:26:47 -08:00
constructor ( path : string , redirectTo : string ) {
if ( path . startsWith ( '/' ) ) {
path = path . substring ( 1 ) ;
2015-06-30 13:18:51 -07:00
}
2015-11-23 16:26:47 -08:00
this . segments = path . split ( '/' ) ;
if ( redirectTo . startsWith ( '/' ) ) {
redirectTo = redirectTo . substring ( 1 ) ;
}
this . toSegments = redirectTo . split ( '/' ) ;
}
2015-07-17 13:36:53 -07:00
2015-11-23 16:26:47 -08:00
/ * *
* Returns ` null ` or a ` ParsedUrl ` representing the new path to match
* /
redirect ( urlParse : Url ) : Url {
for ( var i = 0 ; i < this . segments . length ; i += 1 ) {
if ( isBlank ( urlParse ) ) {
return null ;
}
let segment = this . segments [ i ] ;
if ( segment != urlParse . path ) {
return null ;
}
urlParse = urlParse . child ;
2015-06-30 13:18:51 -07:00
}
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
for ( var i = this . toSegments . length - 1 ; i >= 0 ; i -= 1 ) {
let segment = this . toSegments [ i ] ;
urlParse = new Url ( segment , urlParse ) ;
}
return urlParse ;
2015-05-15 02:05:57 -07:00
}
}