diff --git a/modules/@angular/router/src/create_router_state.ts b/modules/@angular/router/src/create_router_state.ts new file mode 100644 index 0000000000..10b686139a --- /dev/null +++ b/modules/@angular/router/src/create_router_state.ts @@ -0,0 +1,42 @@ +import { RouterStateCandidate, ActivatedRouteCandidate, RouterState, ActivatedRoute } from './router_state'; +import { TreeNode } from './utils/tree'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +export function createRouterState(curr: RouterStateCandidate, prev: RouterStateCandidate, prevState: RouterState): RouterState { + const root = createNode(curr._root, prev ? prev._root : null, prevState ? prevState._root : null); + const queryParams = prevState ? prevState.queryParams : new BehaviorSubject(curr.queryParams); + const fragment = prevState ? prevState.fragment : new BehaviorSubject(curr.fragment); + return new RouterState(root, queryParams, fragment); +} + +function createNode(curr:TreeNode, prev?:TreeNode, prevState?:TreeNode):TreeNode { + if (prev && equalRouteCandidates(prev.value, curr.value)) { + const value = prevState.value; + const children = createOrReuseChildren(curr, prev, prevState); + return new TreeNode(value, children); + + } else { + const value = createActivatedRoute(curr.value); + const children = curr.children.map(c => createNode(c)); + return new TreeNode(value, children); + } +} + +function createOrReuseChildren(curr:TreeNode, prev:TreeNode, prevState:TreeNode) { + return curr.children.map(child => { + const index = prev.children.findIndex(p => equalRouteCandidates(p.value, child.value)); + if (index >= 0) { + return createNode(child, prev.children[index], prevState.children[index]); + } else { + return createNode(child); + } + }); +} + +function createActivatedRoute(c:ActivatedRouteCandidate) { + return new ActivatedRoute(new BehaviorSubject(c.urlSegments), new BehaviorSubject(c.params), c.outlet, c.component); +} + +function equalRouteCandidates(a: ActivatedRouteCandidate, b: ActivatedRouteCandidate): boolean { + return a._routeConfig === b._routeConfig; +} \ No newline at end of file diff --git a/modules/@angular/router/test/create_router_state.spec.ts b/modules/@angular/router/test/create_router_state.spec.ts new file mode 100644 index 0000000000..556b7877e8 --- /dev/null +++ b/modules/@angular/router/test/create_router_state.spec.ts @@ -0,0 +1,72 @@ +import {DefaultUrlSerializer} from '../src/url_serializer'; +import {UrlTree} from '../src/url_tree'; +import {Params, PRIMARY_OUTLET} from '../src/shared'; +import {ActivatedRoute, ActivatedRouteCandidate, RouterStateCandidate, createEmptyStateCandidate, createEmptyState} from '../src/router_state'; +import {createRouterState} from '../src/create_router_state'; +import {recognize} from '../src/recognize'; +import {RouterConfig} from '../src/config'; + +describe('create router state', () => { + const emptyCandidate = () => createEmptyStateCandidate(RootComponent); + const emptyState = () => createEmptyState(RootComponent); + + it('should work create new state', () => { + const state = createRouterState(createState([ + {path: 'a', component: ComponentA}, + {path: 'b', component: ComponentB, outlet: 'left'}, + {path: 'c', component: ComponentC, outlet: 'right'} + ], "a(left:b//right:c)"), emptyCandidate(), emptyState()); + + checkActivatedRoute(state.root, RootComponent); + + const c = state.children(state.root); + checkActivatedRoute(c[0], ComponentA); + checkActivatedRoute(c[1], ComponentB, 'left'); + checkActivatedRoute(c[2], ComponentC, 'right'); + }); + + it('should reuse existing nodes when it can', () => { + const config = [ + {path: 'a', component: ComponentA}, + {path: 'b', component: ComponentB, outlet: 'left'}, + {path: 'c', component: ComponentC, outlet: 'left'} + ]; + + const prevCandidate = createState(config, "a(left:b)"); + const prevState = createRouterState(prevCandidate, emptyCandidate(), emptyState()); + + const state = createRouterState(createState(config, "a(left:c)"), prevCandidate, prevState); + + expect(prevState.root).toBe(state.root); + const prevC = prevState.children(prevState.root); + const currC = state.children(state.root); + + expect(prevC[0]).toBe(currC[0]); + expect(prevC[1]).not.toBe(currC[1]); + checkActivatedRoute(currC[1], ComponentC, 'left'); + }); +}); + +function createState(config: RouterConfig, url: string): RouterStateCandidate { + let res; + recognize(RootComponent, config, tree(url)).forEach(s => res = s); + return res; +} + +function checkActivatedRoute(actual: ActivatedRoute | null, cmp: Function, outlet: string = PRIMARY_OUTLET):void { + if (actual === null) { + expect(actual).toBeDefined(); + } else { + expect(actual.component).toBe(cmp); + expect(actual.outlet).toEqual(outlet); + } +} + +function tree(url: string): UrlTree { + return new DefaultUrlSerializer().parse(url); +} + +class RootComponent {} +class ComponentA {} +class ComponentB {} +class ComponentC {}