Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor router hash consification #3909

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
59 changes: 19 additions & 40 deletions modules/angular1_router/src/ng_outlet.js
Expand Up @@ -132,62 +132,41 @@ function ngOutletDirective($animate, $injector, $q, $router, $componentMapper, $
}
}

router.registerOutlet({
commit: function (instruction) {
router.registerPrimaryOutlet({
reuse: function (instruction) {
var next = $q.when(true);
var componentInstruction = instruction.component;
if (componentInstruction.reuse) {
var previousInstruction = currentInstruction;
currentInstruction = componentInstruction;
if (currentController.onReuse) {
next = $q.when(currentController.onReuse(currentInstruction, previousInstruction));
}
} else {
var self = this;
next = this.deactivate(instruction).then(function () {
return self.activate(componentInstruction);
});
var previousInstruction = currentInstruction;
currentInstruction = instruction;
if (currentController.onReuse) {
next = $q.when(currentController.onReuse(currentInstruction, previousInstruction));
}
return next.then(function () {
if (childRouter) {
return childRouter.commit(instruction.child);
} else {
return $q.when(true);
}
});

return next;
},
canReuse: function (nextInstruction) {
var result;
var componentInstruction = nextInstruction.component;
if (!currentInstruction ||
currentInstruction.componentType !== componentInstruction.componentType) {
currentInstruction.componentType !== nextInstruction.componentType) {
result = false;
} else if (currentController.canReuse) {
result = currentController.canReuse(componentInstruction, currentInstruction);
result = currentController.canReuse(nextInstruction, currentInstruction);
} else {
result = componentInstruction === currentInstruction ||
angular.equals(componentInstruction.params, currentInstruction.params);
result = nextInstruction === currentInstruction ||
angular.equals(nextInstruction.params, currentInstruction.params);
}
return $q.when(result).then(function (result) {
// TODO: this is a hack
componentInstruction.reuse = result;
return result;
});
return $q.when(result);
},
canDeactivate: function (instruction) {
if (currentInstruction && currentController && currentController.canDeactivate) {
return $q.when(currentController.canDeactivate(instruction && instruction.component, currentInstruction));
return $q.when(currentController.canDeactivate(instruction, currentInstruction));
}
return $q.when(true);
},
deactivate: function (instruction) {
// todo(shahata): childRouter.dectivate, dispose component?
var result = $q.when();
return result.then(function () {
if (currentController && currentController.onDeactivate) {
return currentController.onDeactivate(instruction && instruction.component, currentInstruction);
}
});
if (currentController && currentController.onDeactivate) {
return $q.when(currentController.onDeactivate(instruction, currentInstruction));
}
return $q.when();
},
activate: function (instruction) {
var previousInstruction = currentInstruction;
Expand Down Expand Up @@ -228,7 +207,7 @@ function ngOutletDirective($animate, $injector, $q, $router, $componentMapper, $
}
});
}
}, outletName);
});
}
}

Expand Down
6 changes: 6 additions & 0 deletions modules/angular2/src/router/instruction.ts
Expand Up @@ -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;
Expand Down
31 changes: 24 additions & 7 deletions modules/angular2/src/router/path_recognizer.ts
Expand Up @@ -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

Expand Down Expand Up @@ -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);
}

Expand All @@ -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;
}
}
144 changes: 119 additions & 25 deletions modules/angular2/src/router/router.ts
Expand Up @@ -54,11 +54,16 @@ export class Router {
lastNavigationAttempt: string;

private _currentInstruction: Instruction = null;

private _currentNavigation: Promise<any> = _resolveToTrue;
private _outlet: RouterOutlet = null;
private _auxOutlets: Map<string, RouterOutlet> = new Map();

private _auxRouters: Map<string, Router> = new Map();
private _childRouter: Router;

private _subject: EventEmitter = new EventEmitter();


constructor(public registry: RouteRegistry, public _pipeline: Pipeline, public parent: Router,
public hostComponent: any) {}

Expand All @@ -67,26 +72,75 @@ export class Router {
* Constructs a child router. You probably don't need to use this unless you're writing a reusable
* component.
*/
childRouter(hostComponent: any): Router { return new ChildRouter(this, hostComponent); }
childRouter(hostComponent: any): Router {
return this._childRouter = new ChildRouter(this, hostComponent);
}


/**
* Constructs a child router. You probably don't need to use this unless you're writing a reusable
* component.
*/
auxRouter(hostComponent: any): Router { return new ChildRouter(this, hostComponent); }

/**
* Register an object to notify of route changes. You probably don't need to use this unless
* you're writing a reusable component.
* Register an outlet to notified of primary route changes.
*
* You probably don't need to use this unless you're writing a reusable component.
*/
registerOutlet(outlet: RouterOutlet): Promise<boolean> {
registerPrimaryOutlet(outlet: RouterOutlet): Promise<boolean> {
if (isPresent(outlet.name)) {
this._auxOutlets.set(outlet.name, outlet);
} else {
this._outlet = outlet;
throw new BaseException(`registerAuxOutlet expects to be called with an unnamed outlet.`);
}

this._outlet = outlet;
if (isPresent(this._currentInstruction)) {
return outlet.commit(this._currentInstruction);
return this.commit(this._currentInstruction, false);
}
return _resolveToTrue;
}

/**
* Register an outlet to notified of auxiliary route changes.
*
* You probably don't need to use this unless you're writing a reusable component.
*/
registerAuxOutlet(outlet: RouterOutlet): Promise<boolean> {
var outletName = outlet.name;
if (isBlank(outletName)) {
throw new BaseException(`registerAuxOutlet expects to be called with an outlet with a name.`);
}

// TODO...
// what is the host of an aux route???
var router = this.auxRouter(this.hostComponent);

this._auxRouters.set(outletName, router);
router._outlet = outlet;

var auxInstruction;
if (isPresent(this._currentInstruction) &&
isPresent(auxInstruction = this._currentInstruction.auxInstruction[outletName])) {
return router.commit(auxInstruction);
}
return _resolveToTrue;
}


/**
* Given an instruction, returns `true` if the instruction is currently active,
* otherwise `false`.
*/
isRouteActive(instruction: Instruction): boolean {
var router = this;
while (isPresent(router.parent) && isPresent(instruction.child)) {
router = router.parent;
instruction = instruction.child;
}
return isPresent(this._currentInstruction) &&
this._currentInstruction.component == instruction.component;
}

/**
* Dynamically update the routing configuration and trigger a navigation.
*
Expand Down Expand Up @@ -143,7 +197,7 @@ export class Router {

_navigate(instruction: Instruction, _skipLocationChange: boolean): Promise<any> {
return this._settleInstruction(instruction)
.then((_) => this._reuse(instruction))
.then((_) => this._canReuse(instruction))
.then((_) => this._canActivate(instruction))
.then((result) => {
if (!result) {
Expand Down Expand Up @@ -190,14 +244,18 @@ export class Router {
});
}

_reuse(instruction: Instruction): Promise<any> {
/*
* Recursively set reuse flags
*/
_canReuse(instruction: Instruction): Promise<any> {
if (isBlank(this._outlet)) {
return _resolveToFalse;
}
return this._outlet.canReuse(instruction)
return this._outlet.canReuse(instruction.component)
.then((result) => {
if (isPresent(this._outlet.childRouter) && isPresent(instruction.child)) {
return this._outlet.childRouter._reuse(instruction.child);
instruction.component.reuse = result;
if (isPresent(this._childRouter) && isPresent(instruction.child)) {
return this._childRouter._canReuse(instruction.child);
}
});
}
Expand All @@ -211,19 +269,26 @@ export class Router {
return _resolveToTrue;
}
var next: Promise<boolean>;
if (isPresent(instruction) && instruction.component.reuse) {
var childInstruction: Instruction = null;
var reuse: boolean = false;
var componentInstruction: ComponentInstruction = null;
if (isPresent(instruction)) {
childInstruction = instruction.child;
componentInstruction = instruction.component;
reuse = instruction.component.reuse;
}
if (reuse) {
next = _resolveToTrue;
} else {
next = this._outlet.canDeactivate(instruction);
next = this._outlet.canDeactivate(componentInstruction);
}
// TODO: aux route lifecycle hooks
return next.then((result) => {
if (result == false) {
return false;
}
if (isPresent(this._outlet.childRouter)) {
return this._outlet.childRouter._canDeactivate(isPresent(instruction) ? instruction.child :
null);
if (isPresent(this._childRouter)) {
return this._childRouter._canDeactivate(childInstruction);
}
return true;
});
Expand All @@ -234,13 +299,29 @@ export class Router {
*/
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
this._currentInstruction = instruction;
var next = _resolveToTrue;
var next: Promise<any> = _resolveToTrue;
if (isPresent(this._outlet)) {
next = this._outlet.commit(instruction);
var componentInstruction = instruction.component;
if (componentInstruction.reuse) {
next = this._outlet.reuse(componentInstruction);
} else {
next =
this.deactivate(instruction).then((_) => this._outlet.activate(componentInstruction));
}
if (isPresent(instruction.child)) {
next = next.then((_) => {
if (isPresent(this._childRouter)) {
return this._childRouter.commit(instruction.child);
}
});
}
}

var promises = [];
MapWrapper.forEach(this._auxOutlets,
(outlet, _) => { promises.push(outlet.commit(instruction)); });
MapWrapper.forEach(this._auxRouters, (router, name) => {
promises.push(router.commit(instruction.auxInstruction[name]));
});

return next.then((_) => PromiseWrapper.all(promises));
}

Expand All @@ -262,10 +343,23 @@ export class Router {
* Removes the contents of this router's outlet and all descendant outlets
*/
deactivate(instruction: Instruction): Promise<any> {
var childInstruction: Instruction = null;
var componentInstruction: ComponentInstruction = null;
if (isPresent(instruction)) {
childInstruction = instruction.child;
componentInstruction = instruction.component;
}
var next: Promise<any> = _resolveToTrue;
if (isPresent(this._childRouter)) {
next = this._childRouter.deactivate(childInstruction);
}
if (isPresent(this._outlet)) {
return this._outlet.deactivate(instruction);
next = next.then((_) => this._outlet.deactivate(componentInstruction));
}
return _resolveToTrue;

// TODO: handle aux routes

return next;
}


Expand Down