feat(router): hash-cons ComponentInstructions

This commit is contained in:
Brian Ford 2015-08-30 21:20:18 -07:00
parent 76e1f863a2
commit e1a7e0329c
3 changed files with 41 additions and 7 deletions

View File

@ -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;

View File

@ -192,6 +192,8 @@ export class PathRecognizer {
specificity: number;
terminal: boolean = true;
hash: string;
private cache: Map<string, ComponentInstruction> = new Map<string, ComponentInstruction>();
// 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<string>, _recognizer: PathRecognizer,
params: StringMap<string, any>): 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;
}
}

View File

@ -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);