feat(router): implement a function create router state out of a candidate
This commit is contained in:
parent
2de1030413
commit
46911117f1
42
modules/@angular/router/src/create_router_state.ts
Normal file
42
modules/@angular/router/src/create_router_state.ts
Normal file
@ -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<ActivatedRouteCandidate>, prev?:TreeNode<ActivatedRouteCandidate>, prevState?:TreeNode<ActivatedRoute>):TreeNode<ActivatedRoute> {
|
||||||
|
if (prev && equalRouteCandidates(prev.value, curr.value)) {
|
||||||
|
const value = prevState.value;
|
||||||
|
const children = createOrReuseChildren(curr, prev, prevState);
|
||||||
|
return new TreeNode<ActivatedRoute>(value, children);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const value = createActivatedRoute(curr.value);
|
||||||
|
const children = curr.children.map(c => createNode(c));
|
||||||
|
return new TreeNode<ActivatedRoute>(value, children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createOrReuseChildren(curr:TreeNode<ActivatedRouteCandidate>, prev:TreeNode<ActivatedRouteCandidate>, prevState:TreeNode<ActivatedRoute>) {
|
||||||
|
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;
|
||||||
|
}
|
72
modules/@angular/router/test/create_router_state.spec.ts
Normal file
72
modules/@angular/router/test/create_router_state.spec.ts
Normal file
@ -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 {}
|
Loading…
x
Reference in New Issue
Block a user