From e1a7e0329cbc51f5eb4d05a034760f936ea9ffe1 Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Sun, 30 Aug 2015 21:20:18 -0700 Subject: [PATCH] feat(router): hash-cons ComponentInstructions --- modules/angular2/src/router/instruction.ts | 6 ++++ .../angular2/src/router/path_recognizer.ts | 31 ++++++++++++++----- .../test/router/path_recognizer_spec.ts | 11 +++++++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/modules/angular2/src/router/instruction.ts b/modules/angular2/src/router/instruction.ts index a2a6525c5b..f669d23332 100644 --- a/modules/angular2/src/router/instruction.ts +++ b/modules/angular2/src/router/instruction.ts @@ -83,6 +83,12 @@ function stringifyAux(instruction: Instruction): string { * * `ComponentInstructions` is a public API. Instances of `ComponentInstruction` are passed * to route lifecycle hooks, like {@link CanActivate}. + * + * `ComponentInstruction`s are [https://en.wikipedia.org/wiki/Hash_consing](hash consed). You should + * never construct one yourself with "new." Instead, rely on {@link PathRecognizer} to construct + * `ComponentInstruction`s. + * + * You should not modify this object. It should be treated as immutable. */ export class ComponentInstruction { reuse: boolean = false; diff --git a/modules/angular2/src/router/path_recognizer.ts b/modules/angular2/src/router/path_recognizer.ts index 63592d9526..7b7757d2f9 100644 --- a/modules/angular2/src/router/path_recognizer.ts +++ b/modules/angular2/src/router/path_recognizer.ts @@ -192,6 +192,8 @@ export class PathRecognizer { specificity: number; terminal: boolean = true; hash: string; + private cache: Map = new Map(); + // TODO: cache component instruction instances by params and by ParsedUrl instance @@ -252,23 +254,26 @@ export class PathRecognizer { var auxiliary; var instruction: ComponentInstruction; + var urlParams; + var allParams; if (isPresent(currentSegment)) { // If this is the root component, read query params. Otherwise, read matrix params. var paramsSegment = beginningSegment instanceof RootUrl ? beginningSegment : currentSegment; - var allParams = isPresent(paramsSegment.params) ? - StringMapWrapper.merge(paramsSegment.params, positionalParams) : - positionalParams; + allParams = isPresent(paramsSegment.params) ? + StringMapWrapper.merge(paramsSegment.params, positionalParams) : + positionalParams; - var urlParams = serializeParams(paramsSegment.params); + urlParams = serializeParams(paramsSegment.params); - instruction = new ComponentInstruction(urlPath, urlParams, this, allParams); auxiliary = currentSegment.auxiliary; } else { - instruction = new ComponentInstruction(urlPath, [], this, positionalParams); + allParams = positionalParams; auxiliary = []; + urlParams = []; } + instruction = this._getInstruction(urlPath, urlParams, this, allParams); return new PathMatch(instruction, nextSegment, auxiliary); } @@ -289,6 +294,18 @@ export class PathRecognizer { var nonPositionalParams = paramTokens.getUnused(); var urlParams = serializeParams(nonPositionalParams); - return new ComponentInstruction(urlPath, urlParams, this, params); + return this._getInstruction(urlPath, urlParams, this, params); + } + + private _getInstruction(urlPath: string, urlParams: List, _recognizer: PathRecognizer, + params: StringMap): ComponentInstruction { + var hashKey = urlPath + '?' + urlParams.join('?'); + if (this.cache.has(hashKey)) { + return this.cache.get(hashKey); + } + var instruction = new ComponentInstruction(urlPath, urlParams, _recognizer, params); + this.cache.set(hashKey, instruction); + + return instruction; } } diff --git a/modules/angular2/test/router/path_recognizer_spec.ts b/modules/angular2/test/router/path_recognizer_spec.ts index 5a577dd686..dbe1ecb4fb 100644 --- a/modules/angular2/test/router/path_recognizer_spec.ts +++ b/modules/angular2/test/router/path_recognizer_spec.ts @@ -40,6 +40,17 @@ export function main() { .toThrowError(`Path "hi//there" contains "//" which is not allowed in a route config.`); }); + it('should return the same instruction instance when recognizing the same path', () => { + var rec = new PathRecognizer('/one', mockRouteHandler); + + var one = new Url('one', null, null, {}); + + var firstMatch = rec.recognize(one); + var secondMatch = rec.recognize(one); + + expect(firstMatch.instruction).toBe(secondMatch.instruction); + }); + describe('querystring params', () => { it('should parse querystring params so long as the recognizer is a root', () => { var rec = new PathRecognizer('/hello/there', mockRouteHandler);