feat(router): implement recognizer
This commit is contained in:
		
							parent
							
								
									f6985671dd
								
							
						
					
					
						commit
						ef6163e652
					
				
							
								
								
									
										84
									
								
								modules/angular2/src/alt_router/recognize.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								modules/angular2/src/alt_router/recognize.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | ||||
| import {RouteSegment, UrlSegment, Tree} from './segments'; | ||||
| import {RoutesMetadata, RouteMetadata} from './metadata/metadata'; | ||||
| import {Type, isPresent, stringify} from 'angular2/src/facade/lang'; | ||||
| import {PromiseWrapper} from 'angular2/src/facade/promise'; | ||||
| import {BaseException} from 'angular2/src/facade/exceptions'; | ||||
| import {ComponentResolver} from 'angular2/core'; | ||||
| import {reflector} from 'angular2/src/core/reflection/reflection'; | ||||
| 
 | ||||
| export function recognize(componentResolver: ComponentResolver, type: Type, | ||||
|                           url: Tree<UrlSegment>): Promise<Tree<RouteSegment>> { | ||||
|   return _recognize(componentResolver, type, url, url.root) | ||||
|       .then(nodes => new Tree<RouteSegment>(nodes)); | ||||
| } | ||||
| 
 | ||||
| function _recognize(componentResolver: ComponentResolver, type: Type, url: Tree<UrlSegment>, | ||||
|                     current: UrlSegment): Promise<RouteSegment[]> { | ||||
|   let metadata = _readMetadata(type);  // should read from the factory instead
 | ||||
| 
 | ||||
|   let matched; | ||||
|   try { | ||||
|     matched = _match(metadata, url, current); | ||||
|   } catch (e) { | ||||
|     return PromiseWrapper.reject(e, null); | ||||
|   } | ||||
| 
 | ||||
|   return componentResolver.resolveComponent(matched.route.component) | ||||
|       .then(factory => { | ||||
|         let segment = new RouteSegment(matched.consumedUrlSegments, matched.parameters, "", | ||||
|                                        matched.route.component, factory); | ||||
| 
 | ||||
|         if (isPresent(matched.leftOver)) { | ||||
|           return _recognize(componentResolver, matched.route.component, url, matched.leftOver) | ||||
|               .then(children => [segment].concat(children)); | ||||
|         } else { | ||||
|           return [segment]; | ||||
|         } | ||||
|       }); | ||||
| } | ||||
| 
 | ||||
| function _match(metadata: RoutesMetadata, url: Tree<UrlSegment>, | ||||
|                 current: UrlSegment): _MatchingResult { | ||||
|   for (let r of metadata.routes) { | ||||
|     let matchingResult = _matchWithParts(r, url, current); | ||||
|     if (isPresent(matchingResult)) { | ||||
|       return matchingResult; | ||||
|     } | ||||
|   } | ||||
|   throw new BaseException("Cannot match any routes"); | ||||
| } | ||||
| 
 | ||||
| function _matchWithParts(route: RouteMetadata, url: Tree<UrlSegment>, | ||||
|                          current: UrlSegment): _MatchingResult { | ||||
|   let parts = route.path.split("/"); | ||||
|   let parameters = {}; | ||||
|   let consumedUrlSegments = []; | ||||
| 
 | ||||
|   let u = current; | ||||
|   for (let i = 0; i < parts.length; ++i) { | ||||
|     consumedUrlSegments.push(u); | ||||
|     let p = parts[i]; | ||||
|     if (p.startsWith(":")) { | ||||
|       let segment = u.segment; | ||||
|       parameters[p.substring(1)] = segment; | ||||
|     } else if (p != u.segment) { | ||||
|       return null; | ||||
|     } | ||||
|     u = url.firstChild(u); | ||||
|   } | ||||
|   return new _MatchingResult(route, consumedUrlSegments, parameters, u); | ||||
| } | ||||
| 
 | ||||
| class _MatchingResult { | ||||
|   constructor(public route: RouteMetadata, public consumedUrlSegments: UrlSegment[], | ||||
|               public parameters: {[key: string]: string}, public leftOver: UrlSegment) {} | ||||
| } | ||||
| 
 | ||||
| function _readMetadata(componentType: Type) { | ||||
|   let metadata = reflector.annotations(componentType).filter(f => f instanceof RoutesMetadata); | ||||
|   if (metadata.length === 0) { | ||||
|     throw new BaseException( | ||||
|         `Component '${stringify(componentType)}' does not have route configuration`); | ||||
|   } | ||||
|   return metadata[0]; | ||||
| } | ||||
							
								
								
									
										89
									
								
								modules/angular2/test/alt_router/recognize_spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								modules/angular2/test/alt_router/recognize_spec.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| import { | ||||
|   ComponentFixture, | ||||
|   AsyncTestCompleter, | ||||
|   TestComponentBuilder, | ||||
|   beforeEach, | ||||
|   ddescribe, | ||||
|   xdescribe, | ||||
|   describe, | ||||
|   el, | ||||
|   expect, | ||||
|   iit, | ||||
|   inject, | ||||
|   beforeEachProviders, | ||||
|   it, | ||||
|   xit | ||||
| } from 'angular2/testing_internal'; | ||||
| 
 | ||||
| import {recognize} from 'angular2/src/alt_router/recognize'; | ||||
| import {Routes, Route} from 'angular2/alt_router'; | ||||
| import {provide, Component, ComponentResolver} from 'angular2/core'; | ||||
| import {UrlSegment, Tree} from 'angular2/src/alt_router/segments'; | ||||
| 
 | ||||
| export function main() { | ||||
|   describe('recognize', () => { | ||||
|     it('should handle position args', | ||||
|        inject([AsyncTestCompleter, ComponentResolver], (async, resolver) => { | ||||
|          recognize(resolver, ComponentA, tree(["b", "paramB", "c", "paramC"])) | ||||
|              .then(r => { | ||||
|                let b = r.root; | ||||
|                expect(stringifyUrl(b.urlSegments)).toEqual(["b", "paramB"]); | ||||
|                expect(b.type).toBe(ComponentB); | ||||
| 
 | ||||
|                let c = r.firstChild(r.root); | ||||
|                expect(stringifyUrl(c.urlSegments)).toEqual(["c", "paramC"]); | ||||
|                expect(c.type).toBe(ComponentC); | ||||
| 
 | ||||
|                async.done(); | ||||
|              }); | ||||
|        })); | ||||
| 
 | ||||
|     it('should error when no matching routes', | ||||
|        inject([AsyncTestCompleter, ComponentResolver], (async, resolver) => { | ||||
|          recognize(resolver, ComponentA, tree(["invalid"])) | ||||
|              .catch(e => { | ||||
|                expect(e.message).toEqual("Cannot match any routes"); | ||||
|                async.done(); | ||||
|              }); | ||||
|        })); | ||||
| 
 | ||||
|     it("should error when a component doesn't have @Routes", | ||||
|        inject([AsyncTestCompleter, ComponentResolver], (async, resolver) => { | ||||
|          recognize(resolver, ComponentA, tree(["d", "invalid"])) | ||||
|              .catch(e => { | ||||
|                expect(e.message) | ||||
|                    .toEqual("Component 'ComponentD' does not have route configuration"); | ||||
|                async.done(); | ||||
|              }); | ||||
|        })); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function tree(nodes: string[]) { | ||||
|   return new Tree<UrlSegment>(nodes.map(v => new UrlSegment(v, {}, ""))); | ||||
| } | ||||
| 
 | ||||
| function stringifyUrl(segments: UrlSegment[]): string[] { | ||||
|   return segments.map(s => s.segment); | ||||
| } | ||||
| 
 | ||||
| @Component({selector: 'c', template: 't'}) | ||||
| class ComponentC { | ||||
| } | ||||
| 
 | ||||
| @Component({selector: 'd', template: 't'}) | ||||
| class ComponentD { | ||||
| } | ||||
| 
 | ||||
| @Component({selector: 'b', template: 't'}) | ||||
| @Routes([new Route({path: "c/:c", component: ComponentC})]) | ||||
| class ComponentB { | ||||
| } | ||||
| 
 | ||||
| @Component({selector: 'a', template: 't'}) | ||||
| @Routes([ | ||||
|   new Route({path: "b/:b", component: ComponentB}), | ||||
|   new Route({path: "d", component: ComponentD}) | ||||
| ]) | ||||
| class ComponentA { | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user