From 550ab31bd0726be61c3f1d05631c0b9bac204333 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Tue, 2 Aug 2016 14:34:00 -0700 Subject: [PATCH] feat(router): add parent, children, firstChild to ActivatedRoute --- modules/@angular/router/src/router_state.ts | 30 +++++- modules/@angular/router/src/utils/tree.ts | 15 +++ .../@angular/router/test/router_state.spec.ts | 95 +++++++++++++++++++ modules/@angular/router/tsconfig.json | 1 + 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 modules/@angular/router/test/router_state.spec.ts diff --git a/modules/@angular/router/src/router_state.ts b/modules/@angular/router/src/router_state.ts index 553da3136e..383595cbbc 100644 --- a/modules/@angular/router/src/router_state.ts +++ b/modules/@angular/router/src/router_state.ts @@ -42,6 +42,7 @@ export class RouterState extends Tree { root: TreeNode, public queryParams: Observable, public fragment: Observable, public snapshot: RouterStateSnapshot) { super(root); + setRouterStateSnapshot(this, root); } toString(): string { return this.snapshot.toString(); } @@ -95,6 +96,9 @@ export class ActivatedRoute { _futureSnapshot: ActivatedRouteSnapshot; snapshot: ActivatedRouteSnapshot; + /** @internal */ + _routerState: RouterState; + /** * @internal */ @@ -107,6 +111,14 @@ export class ActivatedRoute { get routeConfig(): Route { return this._futureSnapshot.routeConfig; } + get parent(): ActivatedRoute { return this._routerState.parent(this); } + + get firstChild(): ActivatedRoute { return this._routerState.firstChild(this); } + + get children(): ActivatedRoute[] { return this._routerState.children(this); } + + get pathFromRoot(): ActivatedRoute[] { return this._routerState.pathFromRoot(this); } + toString(): string { return this.snapshot ? this.snapshot.toString() : `Future(${this._futureSnapshot})`; } @@ -168,6 +180,9 @@ export class ActivatedRouteSnapshot { /** @internal */ _resolve: InheritedResolve; + /** @internal */ + _routerState: RouterStateSnapshot; + /** * @internal */ @@ -183,6 +198,14 @@ export class ActivatedRouteSnapshot { get routeConfig(): Route { return this._routeConfig; } + get parent(): ActivatedRouteSnapshot { return this._routerState.parent(this); } + + get firstChild(): ActivatedRouteSnapshot { return this._routerState.firstChild(this); } + + get children(): ActivatedRouteSnapshot[] { return this._routerState.children(this); } + + get pathFromRoot(): ActivatedRouteSnapshot[] { return this._routerState.pathFromRoot(this); } + toString(): string { const url = this.url.map(s => s.toString()).join('/'); const matched = this._routeConfig ? this._routeConfig.path : ''; @@ -213,17 +236,22 @@ export class RouterStateSnapshot extends Tree { public url: string, root: TreeNode, public queryParams: Params, public fragment: string) { super(root); + setRouterStateSnapshot(this, root); } toString(): string { return serializeNode(this._root); } } +function setRouterStateSnapshot(state: U, node: TreeNode): void { + node.value._routerState = state; + node.children.forEach(c => setRouterStateSnapshot(state, c)); +} + function serializeNode(node: TreeNode): string { const c = node.children.length > 0 ? ` { ${node.children.map(serializeNode).join(", ")} } ` : ''; return `${node.value}${c}`; } - /** * The expectation is that the activate route is created with the right set of parameters. * So we push new values into the observables only when they are not the initial values. diff --git a/modules/@angular/router/src/utils/tree.ts b/modules/@angular/router/src/utils/tree.ts index 755a2a17ae..ad49901e7a 100644 --- a/modules/@angular/router/src/utils/tree.ts +++ b/modules/@angular/router/src/utils/tree.ts @@ -14,21 +14,33 @@ export class Tree { get root(): T { return this._root.value; } + /** + * @deprecated (use ActivatedRoute.parent instead) + */ parent(t: T): T { const p = this.pathFromRoot(t); return p.length > 1 ? p[p.length - 2] : null; } + /** + * @deprecated (use ActivatedRoute.children instead) + */ children(t: T): T[] { const n = findNode(t, this._root); return n ? n.children.map(t => t.value) : []; } + /** + * @deprecated (use ActivatedRoute.firstChild instead) + */ firstChild(t: T): T { const n = findNode(t, this._root); return n && n.children.length > 0 ? n.children[0].value : null; } + /** + * @deprecated + */ siblings(t: T): T[] { const p = findPath(t, this._root, []); if (p.length < 2) return []; @@ -37,6 +49,9 @@ export class Tree { return c.filter(cc => cc !== t); } + /** + * @deprecated (use ActivatedRoute.pathFromRoot instead) + */ pathFromRoot(t: T): T[] { return findPath(t, this._root, []).map(s => s.value); } } diff --git a/modules/@angular/router/test/router_state.spec.ts b/modules/@angular/router/test/router_state.spec.ts new file mode 100644 index 0000000000..f7180bf61b --- /dev/null +++ b/modules/@angular/router/test/router_state.spec.ts @@ -0,0 +1,95 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from '../src/router_state'; +import {TreeNode} from '../src/utils/tree'; + +describe('RouterState & Snapshot', () => { + describe('RouterStateSnapshot', () => { + let state: RouterStateSnapshot; + let a: ActivatedRouteSnapshot; + let b: ActivatedRouteSnapshot; + let c: ActivatedRouteSnapshot; + + beforeEach(() => { + a = createActivatedRouteSnapshot('a'); + b = createActivatedRouteSnapshot('b'); + c = createActivatedRouteSnapshot('c'); + + const root = new TreeNode(a, [new TreeNode(b, []), new TreeNode(c, [])]); + + state = new RouterStateSnapshot('url', root, {}, ''); + }); + + it('should return first child', () => { expect(state.root.firstChild).toBe(b); }); + + it('should return children', () => { + const cc = state.root.children; + expect(cc[0]).toBe(b); + expect(cc[1]).toBe(c); + }); + + it('should return root', () => { + const b = state.root.firstChild; + expect(b.parent).toBe(state.root); + }); + + it('should return path from root', () => { + const b = state.root.firstChild; + const p = b.pathFromRoot; + expect(p[0]).toBe(state.root); + expect(p[1]).toBe(b); + }); + }); + + describe('RouterState', () => { + let state: RouterState; + let a: ActivatedRoute; + let b: ActivatedRoute; + let c: ActivatedRoute; + + beforeEach(() => { + a = createActivatedRoute('a'); + b = createActivatedRoute('b'); + c = createActivatedRoute('c'); + + const root = new TreeNode(a, [new TreeNode(b, []), new TreeNode(c, [])]); + + state = new RouterState(root, null, null, null); + }); + + it('should return first child', () => { expect(state.root.firstChild).toBe(b); }); + + it('should return children', () => { + const cc = state.root.children; + expect(cc[0]).toBe(b); + expect(cc[1]).toBe(c); + }); + + it('should return root', () => { + const b = state.root.firstChild; + expect(b.parent).toBe(state.root); + }); + + it('should return path from root', () => { + const b = state.root.firstChild; + const p = b.pathFromRoot; + expect(p[0]).toBe(state.root); + expect(p[1]).toBe(b); + }); + }); +}); + +function createActivatedRouteSnapshot(cmp: string) { + return new ActivatedRouteSnapshot( + null, null, null, null, cmp, null, null, -1, null); +} + +function createActivatedRoute(cmp: string) { + return new ActivatedRoute(null, null, null, null, cmp, null); +} diff --git a/modules/@angular/router/tsconfig.json b/modules/@angular/router/tsconfig.json index 0ab29fc932..f7e3ba2e35 100644 --- a/modules/@angular/router/tsconfig.json +++ b/modules/@angular/router/tsconfig.json @@ -32,6 +32,7 @@ "test/create_router_state.spec.ts", "test/create_url_tree.spec.ts", "test/config.spec.ts", + "test/router_state.spec.ts", "test/router.spec.ts", "../../../node_modules/@types/jasmine/index.d.ts" ]