parent
f5f85bb528
commit
a9a552c112
|
@ -24,7 +24,6 @@ export class Instruction {
|
||||||
// "capturedUrl" is the part of the URL captured by this instruction
|
// "capturedUrl" is the part of the URL captured by this instruction
|
||||||
// "accumulatedUrl" is the part of the URL captured by this instruction and all children
|
// "accumulatedUrl" is the part of the URL captured by this instruction and all children
|
||||||
accumulatedUrl: string;
|
accumulatedUrl: string;
|
||||||
|
|
||||||
reuse: boolean = false;
|
reuse: boolean = false;
|
||||||
specificity: number;
|
specificity: number;
|
||||||
|
|
||||||
|
@ -50,23 +49,4 @@ export class Instruction {
|
||||||
}
|
}
|
||||||
return this._params;
|
return this._params;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChild(): boolean { return isPresent(this.child); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a currently active instruction and sets a reuse flag on each of this instruction's
|
|
||||||
* children
|
|
||||||
*/
|
|
||||||
reuseComponentsFrom(oldInstruction: Instruction): void {
|
|
||||||
var nextInstruction = this;
|
|
||||||
while (nextInstruction.reuse = shouldReuseComponent(nextInstruction, oldInstruction) &&
|
|
||||||
isPresent(oldInstruction = oldInstruction.child) &&
|
|
||||||
isPresent(nextInstruction = nextInstruction.child))
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldReuseComponent(instr1: Instruction, instr2: Instruction): boolean {
|
|
||||||
return instr1.component == instr2.component &&
|
|
||||||
StringMapWrapper.equals(instr1.params(), instr2.params());
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import {Instruction} from './instruction';
|
||||||
|
import {global} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
// This is here only so that after TS transpilation the file is not empty.
|
||||||
|
// TODO(rado): find a better way to fix this, or remove if likely culprit
|
||||||
|
// https://github.com/systemjs/systemjs/issues/487 gets closed.
|
||||||
|
var __ignore_me = global;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines route lifecycle method [onActivate]
|
||||||
|
*/
|
||||||
|
export interface OnActivate {
|
||||||
|
onActivate(nextInstruction: Instruction, prevInstruction: Instruction): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines route lifecycle method [onReuse]
|
||||||
|
*/
|
||||||
|
export interface OnReuse {
|
||||||
|
onReuse(nextInstruction: Instruction, prevInstruction: Instruction): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines route lifecycle method [onDeactivate]
|
||||||
|
*/
|
||||||
|
export interface OnDeactivate {
|
||||||
|
onDeactivate(nextInstruction: Instruction, prevInstruction: Instruction): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines route lifecycle method [canReuse]
|
||||||
|
*/
|
||||||
|
export interface CanReuse {
|
||||||
|
canReuse(nextInstruction: Instruction, prevInstruction: Instruction): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines route lifecycle method [canDeactivate]
|
||||||
|
*/
|
||||||
|
export interface CanDeactivate {
|
||||||
|
canDeactivate(nextInstruction: Instruction, prevInstruction: Instruction): any;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* This indirection is needed for TS compilation path.
|
||||||
|
* See comment in lifecycle_annotations.ts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
library angular2.router.lifecycle_annotations;
|
||||||
|
|
||||||
|
export "./lifecycle_annotations_impl.dart";
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* This indirection is needed to free up Component, etc symbols in the public API
|
||||||
|
* to be used by the decorator versions of these annotations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {makeDecorator} from 'angular2/src/util/decorators';
|
||||||
|
import {CanActivate as CanActivateAnnotation} from './lifecycle_annotations_impl';
|
||||||
|
|
||||||
|
export {
|
||||||
|
canReuse,
|
||||||
|
canDeactivate,
|
||||||
|
onActivate,
|
||||||
|
onReuse,
|
||||||
|
onDeactivate
|
||||||
|
} from './lifecycle_annotations_impl';
|
||||||
|
|
||||||
|
export var CanActivate = makeDecorator(CanActivateAnnotation);
|
|
@ -0,0 +1,18 @@
|
||||||
|
import {CONST, CONST_EXPR} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
@CONST()
|
||||||
|
export class RouteLifecycleHook {
|
||||||
|
constructor(public name: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CONST()
|
||||||
|
export class CanActivate {
|
||||||
|
constructor(public fn: Function) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const canReuse: RouteLifecycleHook = CONST_EXPR(new RouteLifecycleHook("canReuse"));
|
||||||
|
export const canDeactivate: RouteLifecycleHook =
|
||||||
|
CONST_EXPR(new RouteLifecycleHook("canDeactivate"));
|
||||||
|
export const onActivate: RouteLifecycleHook = CONST_EXPR(new RouteLifecycleHook("onActivate"));
|
||||||
|
export const onReuse: RouteLifecycleHook = CONST_EXPR(new RouteLifecycleHook("onReuse"));
|
||||||
|
export const onDeactivate: RouteLifecycleHook = CONST_EXPR(new RouteLifecycleHook("onDeactivate"));
|
|
@ -0,0 +1,38 @@
|
||||||
|
library angular.router.route_lifecycle_reflector;
|
||||||
|
|
||||||
|
import 'package:angular2/src/router/lifecycle_annotations_impl.dart';
|
||||||
|
import 'package:angular2/src/router/interfaces.dart';
|
||||||
|
import 'package:angular2/src/reflection/reflection.dart';
|
||||||
|
|
||||||
|
bool hasLifecycleHook(RouteLifecycleHook e, type) {
|
||||||
|
if (type is! Type) return false;
|
||||||
|
|
||||||
|
final List interfaces = reflector.interfaces(type);
|
||||||
|
var interface;
|
||||||
|
|
||||||
|
if (e == onActivate) {
|
||||||
|
interface = OnActivate;
|
||||||
|
} else if (e == onDeactivate) {
|
||||||
|
interface = OnDeactivate;
|
||||||
|
} else if (e == onReuse) {
|
||||||
|
interface = OnReuse;
|
||||||
|
} else if (e == canDeactivate) {
|
||||||
|
interface = CanDeactivate;
|
||||||
|
} else if (e == canReuse) {
|
||||||
|
interface = CanReuse;
|
||||||
|
}
|
||||||
|
|
||||||
|
return interfaces.contains(interface);
|
||||||
|
}
|
||||||
|
|
||||||
|
Function getCanActivateHook(type) {
|
||||||
|
final List annotations = reflector.annotations(type);
|
||||||
|
|
||||||
|
for (var annotation in annotations) {
|
||||||
|
if (annotation is CanActivate) {
|
||||||
|
return annotation.fn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import {Type, isPresent} from 'angular2/src/facade/lang';
|
||||||
|
import {RouteLifecycleHook, CanActivate} from './lifecycle_annotations_impl';
|
||||||
|
import {reflector} from 'angular2/src/reflection/reflection';
|
||||||
|
|
||||||
|
export function hasLifecycleHook(e: RouteLifecycleHook, type): boolean {
|
||||||
|
if (!(type instanceof Type)) return false;
|
||||||
|
return e.name in(<any>type).prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCanActivateHook(type): Function {
|
||||||
|
var annotations = reflector.annotations(type);
|
||||||
|
for (let i = 0; i < annotations.length; i += 1) {
|
||||||
|
let annotation = annotations[i];
|
||||||
|
if (annotation instanceof CanActivate) {
|
||||||
|
return annotation.fn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -126,7 +126,11 @@ export class RouteRegistry {
|
||||||
this.configFromComponent(componentType);
|
this.configFromComponent(componentType);
|
||||||
|
|
||||||
if (partialMatch.unmatchedUrl.length == 0) {
|
if (partialMatch.unmatchedUrl.length == 0) {
|
||||||
return new Instruction(componentType, partialMatch.matchedUrl, recognizer);
|
if (recognizer.terminal) {
|
||||||
|
return new Instruction(componentType, partialMatch.matchedUrl, recognizer);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.recognize(partialMatch.unmatchedUrl, componentType)
|
return this.recognize(partialMatch.unmatchedUrl, componentType)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {Pipeline} from './pipeline';
|
||||||
import {Instruction} from './instruction';
|
import {Instruction} from './instruction';
|
||||||
import {RouterOutlet} from './router_outlet';
|
import {RouterOutlet} from './router_outlet';
|
||||||
import {Location} from './location';
|
import {Location} from './location';
|
||||||
|
import {getCanActivateHook} from './route_lifecycle_reflector';
|
||||||
|
|
||||||
let _resolveToTrue = PromiseWrapper.resolve(true);
|
let _resolveToTrue = PromiseWrapper.resolve(true);
|
||||||
let _resolveToFalse = PromiseWrapper.resolve(false);
|
let _resolveToFalse = PromiseWrapper.resolve(false);
|
||||||
|
@ -39,7 +40,6 @@ let _resolveToFalse = PromiseWrapper.resolve(false);
|
||||||
export class Router {
|
export class Router {
|
||||||
navigating: boolean = false;
|
navigating: boolean = false;
|
||||||
lastNavigationAttempt: string;
|
lastNavigationAttempt: string;
|
||||||
previousUrl: string = null;
|
|
||||||
|
|
||||||
private _currentInstruction: Instruction = null;
|
private _currentInstruction: Instruction = null;
|
||||||
private _currentNavigation: Promise<any> = _resolveToTrue;
|
private _currentNavigation: Promise<any> = _resolveToTrue;
|
||||||
|
@ -67,7 +67,7 @@ export class Router {
|
||||||
// TODO: sibling routes
|
// TODO: sibling routes
|
||||||
this._outlet = outlet;
|
this._outlet = outlet;
|
||||||
if (isPresent(this._currentInstruction)) {
|
if (isPresent(this._currentInstruction)) {
|
||||||
return outlet.activate(this._currentInstruction);
|
return outlet.commit(this._currentInstruction);
|
||||||
}
|
}
|
||||||
return _resolveToTrue;
|
return _resolveToTrue;
|
||||||
}
|
}
|
||||||
|
@ -109,35 +109,94 @@ export class Router {
|
||||||
* If the given URL does not begin with `/`, the router will navigate relative to this component.
|
* If the given URL does not begin with `/`, the router will navigate relative to this component.
|
||||||
*/
|
*/
|
||||||
navigate(url: string): Promise<any> {
|
navigate(url: string): Promise<any> {
|
||||||
if (this.navigating) {
|
return this._currentNavigation = this._currentNavigation.then((_) => {
|
||||||
return this._currentNavigation;
|
this.lastNavigationAttempt = url;
|
||||||
}
|
|
||||||
this.lastNavigationAttempt = url;
|
|
||||||
return this._currentNavigation = this.recognize(url).then((matchedInstruction) => {
|
|
||||||
if (isBlank(matchedInstruction)) {
|
|
||||||
return _resolveToFalse;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPresent(this._currentInstruction)) {
|
|
||||||
matchedInstruction.reuseComponentsFrom(this._currentInstruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._startNavigating();
|
this._startNavigating();
|
||||||
|
return this._afterPromiseFinishNavigating(this.recognize(url).then((matchedInstruction) => {
|
||||||
var result =
|
if (isBlank(matchedInstruction)) {
|
||||||
this.commit(matchedInstruction)
|
return false;
|
||||||
.then((_) => {
|
}
|
||||||
this._finishNavigating();
|
return this._reuse(matchedInstruction)
|
||||||
ObservableWrapper.callNext(this._subject, matchedInstruction.accumulatedUrl);
|
.then((_) => this._canActivate(matchedInstruction))
|
||||||
});
|
.then((result) => {
|
||||||
|
if (!result) {
|
||||||
return PromiseWrapper.catchError(result, (err) => {
|
return false;
|
||||||
this._finishNavigating();
|
}
|
||||||
throw err;
|
return this._canDeactivate(matchedInstruction)
|
||||||
});
|
.then((result) => {
|
||||||
|
if (result) {
|
||||||
|
return this.commit(matchedInstruction)
|
||||||
|
.then((_) => {
|
||||||
|
this._emitNavigationFinish(matchedInstruction.accumulatedUrl);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _emitNavigationFinish(url): void { ObservableWrapper.callNext(this._subject, url); }
|
||||||
|
|
||||||
|
private _afterPromiseFinishNavigating(promise: Promise<any>): Promise<any> {
|
||||||
|
return PromiseWrapper.catchError(promise.then((_) => this._finishNavigating()), (err) => {
|
||||||
|
this._finishNavigating();
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_reuse(instruction): Promise<any> {
|
||||||
|
if (isBlank(this._outlet)) {
|
||||||
|
return _resolveToFalse;
|
||||||
|
}
|
||||||
|
return this._outlet.canReuse(instruction)
|
||||||
|
.then((result) => {
|
||||||
|
instruction.reuse = result;
|
||||||
|
if (isPresent(this._outlet.childRouter) && isPresent(instruction.child)) {
|
||||||
|
return this._outlet.childRouter._reuse(instruction.child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _canActivate(instruction: Instruction): Promise<boolean> {
|
||||||
|
return canActivateOne(instruction, this._currentInstruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _canDeactivate(instruction: Instruction): Promise<boolean> {
|
||||||
|
if (isBlank(this._outlet)) {
|
||||||
|
return _resolveToTrue;
|
||||||
|
}
|
||||||
|
var next: Promise<boolean>;
|
||||||
|
if (isPresent(instruction) && instruction.reuse) {
|
||||||
|
next = _resolveToTrue;
|
||||||
|
} else {
|
||||||
|
next = this._outlet.canDeactivate(instruction);
|
||||||
|
}
|
||||||
|
return next.then((result) => {
|
||||||
|
if (result == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isPresent(this._outlet.childRouter)) {
|
||||||
|
return this._outlet.childRouter._canDeactivate(isPresent(instruction) ? instruction.child :
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates this router and all descendant routers according to the given instruction
|
||||||
|
*/
|
||||||
|
commit(instruction: Instruction): Promise<any> {
|
||||||
|
this._currentInstruction = instruction;
|
||||||
|
if (isPresent(this._outlet)) {
|
||||||
|
return this._outlet.commit(instruction);
|
||||||
|
}
|
||||||
|
return _resolveToTrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_startNavigating(): void { this.navigating = true; }
|
_startNavigating(): void { this.navigating = true; }
|
||||||
|
|
||||||
_finishNavigating(): void { this.navigating = false; }
|
_finishNavigating(): void { this.navigating = false; }
|
||||||
|
@ -149,24 +208,12 @@ export class Router {
|
||||||
subscribe(onNext): void { ObservableWrapper.subscribe(this._subject, onNext); }
|
subscribe(onNext): void { ObservableWrapper.subscribe(this._subject, onNext); }
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates this router and all descendant routers according to the given instruction
|
|
||||||
*/
|
|
||||||
commit(instruction: Instruction): Promise<any> {
|
|
||||||
this._currentInstruction = instruction;
|
|
||||||
if (isPresent(this._outlet)) {
|
|
||||||
return this._outlet.activate(instruction);
|
|
||||||
}
|
|
||||||
return _resolveToTrue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the contents of this router's outlet and all descendant outlets
|
* Removes the contents of this router's outlet and all descendant outlets
|
||||||
*/
|
*/
|
||||||
deactivate(): Promise<any> {
|
deactivate(instruction: Instruction): Promise<any> {
|
||||||
if (isPresent(this._outlet)) {
|
if (isPresent(this._outlet)) {
|
||||||
return this._outlet.deactivate();
|
return this._outlet.deactivate(instruction);
|
||||||
}
|
}
|
||||||
return _resolveToTrue;
|
return _resolveToTrue;
|
||||||
}
|
}
|
||||||
|
@ -185,11 +232,10 @@ export class Router {
|
||||||
* router has yet to successfully navigate.
|
* router has yet to successfully navigate.
|
||||||
*/
|
*/
|
||||||
renavigate(): Promise<any> {
|
renavigate(): Promise<any> {
|
||||||
var destination = isBlank(this.previousUrl) ? this.lastNavigationAttempt : this.previousUrl;
|
if (isBlank(this.lastNavigationAttempt)) {
|
||||||
if (isBlank(destination)) {
|
|
||||||
return this._currentNavigation;
|
return this._currentNavigation;
|
||||||
}
|
}
|
||||||
return this.navigate(destination);
|
return this.navigate(this.lastNavigationAttempt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -288,3 +334,24 @@ function splitAndFlattenLinkParams(linkParams: List<any>): List<any> {
|
||||||
return accumulation;
|
return accumulation;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canActivateOne(nextInstruction, currentInstruction): Promise<boolean> {
|
||||||
|
var next = _resolveToTrue;
|
||||||
|
if (isPresent(nextInstruction.child)) {
|
||||||
|
next = canActivateOne(nextInstruction.child,
|
||||||
|
isPresent(currentInstruction) ? currentInstruction.child : null);
|
||||||
|
}
|
||||||
|
return next.then((res) => {
|
||||||
|
if (res == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nextInstruction.reuse) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var hook = getCanActivateHook(nextInstruction.component);
|
||||||
|
if (isPresent(hook)) {
|
||||||
|
return hook(nextInstruction, currentInstruction);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
|
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import {Directive, Attribute} from 'angular2/src/core/annotations/decorators';
|
import {Directive, Attribute} from 'angular2/src/core/annotations/decorators';
|
||||||
|
@ -6,8 +7,9 @@ import {DynamicComponentLoader, ComponentRef, ElementRef} from 'angular2/core';
|
||||||
import {Injector, bind, Dependency, undefinedValue} from 'angular2/di';
|
import {Injector, bind, Dependency, undefinedValue} from 'angular2/di';
|
||||||
|
|
||||||
import * as routerMod from './router';
|
import * as routerMod from './router';
|
||||||
import {Instruction, RouteParams} from './instruction'
|
import {Instruction, RouteParams} from './instruction';
|
||||||
|
import * as hookMod from './lifecycle_annotations';
|
||||||
|
import {hasLifecycleHook} from './route_lifecycle_reflector';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A router outlet is a placeholder that Angular dynamically fills based on the application's route.
|
* A router outlet is a placeholder that Angular dynamically fills based on the application's route.
|
||||||
|
@ -18,11 +20,10 @@ import {Instruction, RouteParams} from './instruction'
|
||||||
* <router-outlet></router-outlet>
|
* <router-outlet></router-outlet>
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
@Directive({
|
@Directive({selector: 'router-outlet'})
|
||||||
selector: 'router-outlet'
|
|
||||||
})
|
|
||||||
export class RouterOutlet {
|
export class RouterOutlet {
|
||||||
private _childRouter: routerMod.Router = null;
|
childRouter: routerMod.Router = null;
|
||||||
|
|
||||||
private _componentRef: ComponentRef = null;
|
private _componentRef: ComponentRef = null;
|
||||||
private _currentInstruction: Instruction = null;
|
private _currentInstruction: Instruction = null;
|
||||||
|
|
||||||
|
@ -38,34 +39,100 @@ export class RouterOutlet {
|
||||||
/**
|
/**
|
||||||
* Given an instruction, update the contents of this outlet.
|
* Given an instruction, update the contents of this outlet.
|
||||||
*/
|
*/
|
||||||
activate(instruction: Instruction): Promise<any> {
|
commit(instruction: Instruction): Promise<any> {
|
||||||
// if we're able to reuse the component, we just have to pass along the instruction to the
|
var next;
|
||||||
// component's router
|
if (instruction.reuse) {
|
||||||
// so it can propagate changes to its children
|
next = this._reuse(instruction);
|
||||||
if ((instruction == this._currentInstruction || instruction.reuse) &&
|
} else {
|
||||||
isPresent(this._childRouter)) {
|
next = this.deactivate(instruction).then((_) => this._activate(instruction));
|
||||||
return this._childRouter.commit(instruction.child);
|
|
||||||
}
|
}
|
||||||
|
return next.then((_) => this._commitChild(instruction));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _commitChild(instruction: Instruction): Promise<any> {
|
||||||
|
if (isPresent(this.childRouter)) {
|
||||||
|
return this.childRouter.commit(instruction.child);
|
||||||
|
} else {
|
||||||
|
return PromiseWrapper.resolve(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _activate(instruction: Instruction): Promise<any> {
|
||||||
|
var previousInstruction = this._currentInstruction;
|
||||||
this._currentInstruction = instruction;
|
this._currentInstruction = instruction;
|
||||||
this._childRouter = this._parentRouter.childRouter(instruction.component);
|
this.childRouter = this._parentRouter.childRouter(instruction.component);
|
||||||
var params = new RouteParams(instruction.params());
|
|
||||||
var bindings = Injector.resolve(
|
|
||||||
[bind(RouteParams).toValue(params), bind(routerMod.Router).toValue(this._childRouter)]);
|
|
||||||
|
|
||||||
return this.deactivate()
|
var bindings = Injector.resolve([
|
||||||
.then((_) => this._loader.loadNextToLocation(instruction.component, this._elementRef,
|
bind(RouteParams)
|
||||||
bindings))
|
.toValue(new RouteParams(instruction.params())),
|
||||||
|
bind(routerMod.Router).toValue(this.childRouter)
|
||||||
|
]);
|
||||||
|
return this._loader.loadNextToLocation(instruction.component, this._elementRef, bindings)
|
||||||
.then((componentRef) => {
|
.then((componentRef) => {
|
||||||
this._componentRef = componentRef;
|
this._componentRef = componentRef;
|
||||||
return this._childRouter.commit(instruction.child);
|
if (hasLifecycleHook(hookMod.onActivate, instruction.component)) {
|
||||||
|
return this._componentRef.instance.onActivate(instruction, previousInstruction);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
deactivate(): Promise<any> {
|
/**
|
||||||
return (isPresent(this._childRouter) ? this._childRouter.deactivate() :
|
* Called by Router during recognition phase
|
||||||
PromiseWrapper.resolve(true))
|
*/
|
||||||
|
canDeactivate(nextInstruction: Instruction): Promise<boolean> {
|
||||||
|
if (isBlank(this._currentInstruction)) {
|
||||||
|
return PromiseWrapper.resolve(true);
|
||||||
|
}
|
||||||
|
if (hasLifecycleHook(hookMod.canDeactivate, this._currentInstruction.component)) {
|
||||||
|
return PromiseWrapper.resolve(
|
||||||
|
this._componentRef.instance.canDeactivate(nextInstruction, this._currentInstruction));
|
||||||
|
}
|
||||||
|
return PromiseWrapper.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by Router during recognition phase
|
||||||
|
*/
|
||||||
|
canReuse(nextInstruction: Instruction): Promise<boolean> {
|
||||||
|
var result;
|
||||||
|
if (isBlank(this._currentInstruction) ||
|
||||||
|
this._currentInstruction.component != nextInstruction.component) {
|
||||||
|
result = false;
|
||||||
|
} else if (hasLifecycleHook(hookMod.canReuse, this._currentInstruction.component)) {
|
||||||
|
result = this._componentRef.instance.canReuse(nextInstruction, this._currentInstruction);
|
||||||
|
} else {
|
||||||
|
result = nextInstruction == this._currentInstruction ||
|
||||||
|
StringMapWrapper.equals(nextInstruction.params(), this._currentInstruction.params());
|
||||||
|
}
|
||||||
|
return PromiseWrapper.resolve(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private _reuse(instruction): Promise<any> {
|
||||||
|
var previousInstruction = this._currentInstruction;
|
||||||
|
this._currentInstruction = instruction;
|
||||||
|
return PromiseWrapper.resolve(
|
||||||
|
hasLifecycleHook(hookMod.onReuse, this._currentInstruction.component) ?
|
||||||
|
this._componentRef.instance.onReuse(instruction, previousInstruction) :
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
deactivate(nextInstruction: Instruction): Promise<any> {
|
||||||
|
return (isPresent(this.childRouter) ?
|
||||||
|
this.childRouter.deactivate(isPresent(nextInstruction) ? nextInstruction.child :
|
||||||
|
null) :
|
||||||
|
PromiseWrapper.resolve(true))
|
||||||
|
.then((_) => {
|
||||||
|
if (isPresent(this._componentRef) && isPresent(this._currentInstruction) &&
|
||||||
|
hasLifecycleHook(hookMod.onDeactivate, this._currentInstruction.component)) {
|
||||||
|
return this._componentRef.instance.onDeactivate(nextInstruction,
|
||||||
|
this._currentInstruction);
|
||||||
|
}
|
||||||
|
})
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
if (isPresent(this._componentRef)) {
|
if (isPresent(this._componentRef)) {
|
||||||
this._componentRef.dispose();
|
this._componentRef.dispose();
|
||||||
|
@ -73,9 +140,4 @@ export class RouterOutlet {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
canDeactivate(instruction: Instruction): Promise<boolean> {
|
|
||||||
// TODO: how to get ahold of the component instance here?
|
|
||||||
return PromiseWrapper.resolve(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,8 @@ import {
|
||||||
import {Injector, bind} from 'angular2/di';
|
import {Injector, bind} from 'angular2/di';
|
||||||
import {Component, View} from 'angular2/src/core/annotations/decorators';
|
import {Component, View} from 'angular2/src/core/annotations/decorators';
|
||||||
import * as annotations from 'angular2/src/core/annotations_impl/view';
|
import * as annotations from 'angular2/src/core/annotations_impl/view';
|
||||||
import {CONST, NumberWrapper} from 'angular2/src/facade/lang';
|
import {CONST, NumberWrapper, isPresent} from 'angular2/src/facade/lang';
|
||||||
|
import {Promise, PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||||
|
|
||||||
import {RootRouter} from 'angular2/src/router/router';
|
import {RootRouter} from 'angular2/src/router/router';
|
||||||
import {Pipeline} from 'angular2/src/router/pipeline';
|
import {Pipeline} from 'angular2/src/router/pipeline';
|
||||||
|
@ -30,9 +31,18 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||||
import {Location} from 'angular2/src/router/location';
|
import {Location} from 'angular2/src/router/location';
|
||||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||||
|
import {
|
||||||
|
OnActivate,
|
||||||
|
OnDeactivate,
|
||||||
|
OnReuse,
|
||||||
|
CanDeactivate,
|
||||||
|
CanReuse
|
||||||
|
} from 'angular2/src/router/interfaces';
|
||||||
|
import {CanActivate} from 'angular2/src/router/lifecycle_annotations';
|
||||||
|
import {Instruction} from 'angular2/src/router/instruction';
|
||||||
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
||||||
|
|
||||||
var teamCmpCount;
|
var cmpInstanceCount, log, eventBus, completer;
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('Outlet Directive', () => {
|
describe('Outlet Directive', () => {
|
||||||
|
@ -55,7 +65,9 @@ export function main() {
|
||||||
tcb = tcBuilder;
|
tcb = tcBuilder;
|
||||||
rtr = router;
|
rtr = router;
|
||||||
location = loc;
|
location = loc;
|
||||||
teamCmpCount = 0;
|
cmpInstanceCount = 0;
|
||||||
|
log = '';
|
||||||
|
eventBus = new EventEmitter();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function compile(template: string = "<router-outlet></router-outlet>") {
|
function compile(template: string = "<router-outlet></router-outlet>") {
|
||||||
|
@ -158,13 +170,13 @@ export function main() {
|
||||||
.then((_) => rtr.navigate('/team/angular/user/rado'))
|
.then((_) => rtr.navigate('/team/angular/user/rado'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
rootTC.detectChanges();
|
rootTC.detectChanges();
|
||||||
expect(teamCmpCount).toBe(1);
|
expect(cmpInstanceCount).toBe(1);
|
||||||
expect(rootTC.nativeElement).toHaveText('team angular { hello rado }');
|
expect(rootTC.nativeElement).toHaveText('team angular { hello rado }');
|
||||||
})
|
})
|
||||||
.then((_) => rtr.navigate('/team/angular/user/victor'))
|
.then((_) => rtr.navigate('/team/angular/user/victor'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
rootTC.detectChanges();
|
rootTC.detectChanges();
|
||||||
expect(teamCmpCount).toBe(1);
|
expect(cmpInstanceCount).toBe(1);
|
||||||
expect(rootTC.nativeElement).toHaveText('team angular { hello victor }');
|
expect(rootTC.nativeElement).toHaveText('team angular { hello victor }');
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -228,6 +240,282 @@ export function main() {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should call the onActivate hook', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => rtr.navigate('/on-activate'))
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('activate cmp');
|
||||||
|
expect(log).toEqual('activate: null -> /on-activate;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should wait for a parent component\'s onActivate hook to resolve before calling its child\'s',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => {
|
||||||
|
ObservableWrapper.subscribe(eventBus, (ev) => {
|
||||||
|
if (ev.startsWith('parent activate')) {
|
||||||
|
completer.resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rtr.navigate('/parent-activate/child-activate')
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('parent {activate cmp}');
|
||||||
|
expect(log).toEqual(
|
||||||
|
'parent activate: null -> /parent-activate/child-activate;activate: null -> /child-activate;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should call the onDeactivate hook', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => rtr.navigate('/on-deactivate'))
|
||||||
|
.then((_) => rtr.navigate('/a'))
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('A');
|
||||||
|
expect(log).toEqual('deactivate: /on-deactivate -> /a;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should wait for a child component\'s onDeactivate hook to resolve before calling its parent\'s',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => rtr.navigate('/parent-deactivate/child-deactivate'))
|
||||||
|
.then((_) => {
|
||||||
|
ObservableWrapper.subscribe(eventBus, (ev) => {
|
||||||
|
if (ev.startsWith('deactivate')) {
|
||||||
|
completer.resolve(true);
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('parent {deactivate cmp}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rtr.navigate('/a').then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('A');
|
||||||
|
expect(log).toEqual(
|
||||||
|
'deactivate: /child-deactivate -> null;parent deactivate: /parent-deactivate/child-deactivate -> /a;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should reuse a component when the canReuse hook returns false',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => rtr.navigate('/on-reuse/1/a'))
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(log).toEqual('');
|
||||||
|
expect(rootTC.nativeElement).toHaveText('reuse {A}');
|
||||||
|
expect(cmpInstanceCount).toBe(1);
|
||||||
|
})
|
||||||
|
.then((_) => rtr.navigate('/on-reuse/2/b'))
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(log).toEqual('reuse: /on-reuse/1/a -> /on-reuse/2/b;');
|
||||||
|
expect(rootTC.nativeElement).toHaveText('reuse {B}');
|
||||||
|
expect(cmpInstanceCount).toBe(1);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should not reuse a component when the canReuse hook returns false',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => rtr.navigate('/never-reuse/1/a'))
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(log).toEqual('');
|
||||||
|
expect(rootTC.nativeElement).toHaveText('reuse {A}');
|
||||||
|
expect(cmpInstanceCount).toBe(1);
|
||||||
|
})
|
||||||
|
.then((_) => rtr.navigate('/never-reuse/2/b'))
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(log).toEqual('');
|
||||||
|
expect(rootTC.nativeElement).toHaveText('reuse {B}');
|
||||||
|
expect(cmpInstanceCount).toBe(2);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should navigate when canActivate returns true', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => {
|
||||||
|
ObservableWrapper.subscribe(eventBus, (ev) => {
|
||||||
|
if (ev.startsWith('canActivate')) {
|
||||||
|
completer.resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rtr.navigate('/can-activate/a')
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('canActivate {A}');
|
||||||
|
expect(log).toEqual('canActivate: null -> /can-activate/a;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not navigate when canActivate returns false',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => {
|
||||||
|
ObservableWrapper.subscribe(eventBus, (ev) => {
|
||||||
|
if (ev.startsWith('canActivate')) {
|
||||||
|
completer.resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rtr.navigate('/can-activate/a')
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('');
|
||||||
|
expect(log).toEqual('canActivate: null -> /can-activate/a;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should navigate away when canDeactivate returns true',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => rtr.navigate('/can-deactivate/a'))
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('canDeactivate {A}');
|
||||||
|
expect(log).toEqual('');
|
||||||
|
|
||||||
|
ObservableWrapper.subscribe(eventBus, (ev) => {
|
||||||
|
if (ev.startsWith('canDeactivate')) {
|
||||||
|
completer.resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rtr.navigate('/a').then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('A');
|
||||||
|
expect(log).toEqual('canDeactivate: /can-deactivate/a -> /a;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not navigate away when canDeactivate returns false',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => rtr.navigate('/can-deactivate/a'))
|
||||||
|
.then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('canDeactivate {A}');
|
||||||
|
expect(log).toEqual('');
|
||||||
|
|
||||||
|
ObservableWrapper.subscribe(eventBus, (ev) => {
|
||||||
|
if (ev.startsWith('canDeactivate')) {
|
||||||
|
completer.resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rtr.navigate('/a').then((_) => {
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('canDeactivate {A}');
|
||||||
|
expect(log).toEqual('canDeactivate: /can-deactivate/a -> /a;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should run activation and deactivation hooks in the correct order',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => rtr.navigate('/activation-hooks/child'))
|
||||||
|
.then((_) => {
|
||||||
|
expect(log).toEqual('canActivate child: null -> /child;' +
|
||||||
|
'canActivate parent: null -> /activation-hooks/child;' +
|
||||||
|
'onActivate parent: null -> /activation-hooks/child;' +
|
||||||
|
'onActivate child: null -> /child;');
|
||||||
|
|
||||||
|
log = '';
|
||||||
|
return rtr.navigate('/a');
|
||||||
|
})
|
||||||
|
.then((_) => {
|
||||||
|
expect(log).toEqual('canDeactivate parent: /activation-hooks/child -> /a;' +
|
||||||
|
'canDeactivate child: /child -> null;' +
|
||||||
|
'onDeactivate child: /child -> null;' +
|
||||||
|
'onDeactivate parent: /activation-hooks/child -> /a;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should only run reuse hooks when reusing', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => rtr.navigate('/reuse-hooks/1'))
|
||||||
|
.then((_) => {
|
||||||
|
expect(log).toEqual('canActivate: null -> /reuse-hooks/1;' +
|
||||||
|
'onActivate: null -> /reuse-hooks/1;');
|
||||||
|
|
||||||
|
ObservableWrapper.subscribe(eventBus, (ev) => {
|
||||||
|
if (ev.startsWith('canReuse')) {
|
||||||
|
completer.resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log = '';
|
||||||
|
return rtr.navigate('/reuse-hooks/2');
|
||||||
|
})
|
||||||
|
.then((_) => {
|
||||||
|
expect(log).toEqual('canReuse: /reuse-hooks/1 -> /reuse-hooks/2;' +
|
||||||
|
'onReuse: /reuse-hooks/1 -> /reuse-hooks/2;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not run reuse hooks when not reusing', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
|
||||||
|
.then((_) => rtr.navigate('/reuse-hooks/1'))
|
||||||
|
.then((_) => {
|
||||||
|
expect(log).toEqual('canActivate: null -> /reuse-hooks/1;' +
|
||||||
|
'onActivate: null -> /reuse-hooks/1;');
|
||||||
|
|
||||||
|
ObservableWrapper.subscribe(eventBus, (ev) => {
|
||||||
|
if (ev.startsWith('canReuse')) {
|
||||||
|
completer.resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log = '';
|
||||||
|
return rtr.navigate('/reuse-hooks/2');
|
||||||
|
})
|
||||||
|
.then((_) => {
|
||||||
|
expect(log).toEqual('canReuse: /reuse-hooks/1 -> /reuse-hooks/2;' +
|
||||||
|
'canActivate: /reuse-hooks/1 -> /reuse-hooks/2;' +
|
||||||
|
'canDeactivate: /reuse-hooks/1 -> /reuse-hooks/2;' +
|
||||||
|
'onDeactivate: /reuse-hooks/1 -> /reuse-hooks/2;' +
|
||||||
|
'onActivate: /reuse-hooks/1 -> /reuse-hooks/2;');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
describe('when clicked', () => {
|
describe('when clicked', () => {
|
||||||
|
|
||||||
var clickOnElement = function(view) {
|
var clickOnElement = function(view) {
|
||||||
|
@ -352,7 +640,7 @@ class TeamCmp {
|
||||||
id: string;
|
id: string;
|
||||||
constructor(params: RouteParams) {
|
constructor(params: RouteParams) {
|
||||||
this.id = params.get('id');
|
this.id = params.get('id');
|
||||||
teamCmpCount += 1;
|
cmpInstanceCount += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,3 +649,175 @@ class TeamCmp {
|
||||||
class MyComp {
|
class MyComp {
|
||||||
name;
|
name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logHook(name: string, next: Instruction, prev: Instruction) {
|
||||||
|
var message = name + ': ' + (isPresent(prev) ? prev.accumulatedUrl : 'null') + ' -> ' +
|
||||||
|
(isPresent(next) ? next.accumulatedUrl : 'null') + ';';
|
||||||
|
log += message;
|
||||||
|
ObservableWrapper.callNext(eventBus, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'activate-cmp'})
|
||||||
|
@View({template: 'activate cmp'})
|
||||||
|
class ActivateCmp implements OnActivate {
|
||||||
|
onActivate(next: Instruction, prev: Instruction) { logHook('activate', next, prev); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'parent-activate-cmp'})
|
||||||
|
@View({template: `parent {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([{path: '/child-activate', component: ActivateCmp}])
|
||||||
|
class ParentActivateCmp implements OnActivate {
|
||||||
|
onActivate(next: Instruction, prev: Instruction): Promise<any> {
|
||||||
|
completer = PromiseWrapper.completer();
|
||||||
|
logHook('parent activate', next, prev);
|
||||||
|
return completer.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'deactivate-cmp'})
|
||||||
|
@View({template: 'deactivate cmp'})
|
||||||
|
class DeactivateCmp implements OnDeactivate {
|
||||||
|
onDeactivate(next: Instruction, prev: Instruction) { logHook('deactivate', next, prev); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'deactivate-cmp'})
|
||||||
|
@View({template: 'deactivate cmp'})
|
||||||
|
class WaitDeactivateCmp implements OnDeactivate {
|
||||||
|
onDeactivate(next: Instruction, prev: Instruction): Promise<any> {
|
||||||
|
completer = PromiseWrapper.completer();
|
||||||
|
logHook('deactivate', next, prev);
|
||||||
|
return completer.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'parent-deactivate-cmp'})
|
||||||
|
@View({template: `parent {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([{path: '/child-deactivate', component: WaitDeactivateCmp}])
|
||||||
|
class ParentDeactivateCmp implements OnDeactivate {
|
||||||
|
onDeactivate(next: Instruction, prev: Instruction) { logHook('parent deactivate', next, prev); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'reuse-cmp'})
|
||||||
|
@View({template: `reuse {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([{path: '/a', component: A}, {path: '/b', component: B}])
|
||||||
|
class ReuseCmp implements OnReuse, CanReuse {
|
||||||
|
constructor() { cmpInstanceCount += 1; }
|
||||||
|
canReuse(next: Instruction, prev: Instruction) { return true; }
|
||||||
|
onReuse(next: Instruction, prev: Instruction) { logHook('reuse', next, prev); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'never-reuse-cmp'})
|
||||||
|
@View({template: `reuse {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([{path: '/a', component: A}, {path: '/b', component: B}])
|
||||||
|
class NeverReuseCmp implements OnReuse, CanReuse {
|
||||||
|
constructor() { cmpInstanceCount += 1; }
|
||||||
|
canReuse(next: Instruction, prev: Instruction) { return false; }
|
||||||
|
onReuse(next: Instruction, prev: Instruction) { logHook('reuse', next, prev); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'can-activate-cmp'})
|
||||||
|
@View({template: `canActivate {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([{path: '/a', component: A}, {path: '/b', component: B}])
|
||||||
|
@CanActivate(CanActivateCmp.canActivate)
|
||||||
|
class CanActivateCmp {
|
||||||
|
static canActivate(next: Instruction, prev: Instruction) {
|
||||||
|
completer = PromiseWrapper.completer();
|
||||||
|
logHook('canActivate', next, prev);
|
||||||
|
return completer.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'can-deactivate-cmp'})
|
||||||
|
@View({template: `canDeactivate {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([{path: '/a', component: A}, {path: '/b', component: B}])
|
||||||
|
class CanDeactivateCmp implements CanDeactivate {
|
||||||
|
canDeactivate(next: Instruction, prev: Instruction) {
|
||||||
|
completer = PromiseWrapper.completer();
|
||||||
|
logHook('canDeactivate', next, prev);
|
||||||
|
return completer.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'all-hooks-child-cmp'})
|
||||||
|
@View({template: `child`})
|
||||||
|
@CanActivate(AllHooksChildCmp.canActivate)
|
||||||
|
class AllHooksChildCmp implements CanDeactivate, OnDeactivate, OnActivate {
|
||||||
|
canDeactivate(next: Instruction, prev: Instruction) {
|
||||||
|
logHook('canDeactivate child', next, prev);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeactivate(next: Instruction, prev: Instruction) { logHook('onDeactivate child', next, prev); }
|
||||||
|
|
||||||
|
static canActivate(next: Instruction, prev: Instruction) {
|
||||||
|
logHook('canActivate child', next, prev);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivate(next: Instruction, prev: Instruction) { logHook('onActivate child', next, prev); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'all-hooks-parent-cmp'})
|
||||||
|
@View({template: `<router-outlet></router-outlet>`, directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([{path: '/child', component: AllHooksChildCmp}])
|
||||||
|
@CanActivate(AllHooksParentCmp.canActivate)
|
||||||
|
class AllHooksParentCmp implements CanDeactivate, OnDeactivate, OnActivate {
|
||||||
|
canDeactivate(next: Instruction, prev: Instruction) {
|
||||||
|
logHook('canDeactivate parent', next, prev);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeactivate(next: Instruction, prev: Instruction) { logHook('onDeactivate parent', next, prev); }
|
||||||
|
|
||||||
|
static canActivate(next: Instruction, prev: Instruction) {
|
||||||
|
logHook('canActivate parent', next, prev);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivate(next: Instruction, prev: Instruction) { logHook('onActivate parent', next, prev); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'reuse-hooks-cmp'})
|
||||||
|
@View({template: 'reuse hooks cmp'})
|
||||||
|
@CanActivate(ReuseHooksCmp.canActivate)
|
||||||
|
class ReuseHooksCmp implements OnActivate, OnReuse, OnDeactivate, CanReuse, CanDeactivate {
|
||||||
|
canReuse(next: Instruction, prev: Instruction): Promise<any> {
|
||||||
|
completer = PromiseWrapper.completer();
|
||||||
|
logHook('canReuse', next, prev);
|
||||||
|
return completer.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
onReuse(next: Instruction, prev: Instruction) { logHook('onReuse', next, prev); }
|
||||||
|
|
||||||
|
canDeactivate(next: Instruction, prev: Instruction) {
|
||||||
|
logHook('canDeactivate', next, prev);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeactivate(next: Instruction, prev: Instruction) { logHook('onDeactivate', next, prev); }
|
||||||
|
|
||||||
|
static canActivate(next: Instruction, prev: Instruction) {
|
||||||
|
logHook('canActivate', next, prev);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivate(next: Instruction, prev: Instruction) { logHook('onActivate', next, prev); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'lifecycle-cmp'})
|
||||||
|
@View({template: `<router-outlet></router-outlet>`, directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([
|
||||||
|
{path: '/a', component: A},
|
||||||
|
{path: '/on-activate', component: ActivateCmp},
|
||||||
|
{path: '/parent-activate/...', component: ParentActivateCmp},
|
||||||
|
{path: '/on-deactivate', component: DeactivateCmp},
|
||||||
|
{path: '/parent-deactivate/...', component: ParentDeactivateCmp},
|
||||||
|
{path: '/on-reuse/:number/...', component: ReuseCmp},
|
||||||
|
{path: '/never-reuse/:number/...', component: NeverReuseCmp},
|
||||||
|
{path: '/can-activate/...', component: CanActivateCmp},
|
||||||
|
{path: '/can-deactivate/...', component: CanDeactivateCmp},
|
||||||
|
{path: '/activation-hooks/...', component: AllHooksParentCmp},
|
||||||
|
{path: '/reuse-hooks/:number', component: ReuseHooksCmp}
|
||||||
|
])
|
||||||
|
class LifecycleCmp {
|
||||||
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ export function main() {
|
||||||
router.config({'path': '/', 'component': DummyComponent})
|
router.config({'path': '/', 'component': DummyComponent})
|
||||||
.then((_) => router.registerOutlet(outlet))
|
.then((_) => router.registerOutlet(outlet))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
expect(outlet.spy('activate')).toHaveBeenCalled();
|
expect(outlet.spy('commit')).toHaveBeenCalled();
|
||||||
expect(location.urlChanges).toEqual([]);
|
expect(location.urlChanges).toEqual([]);
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -70,7 +70,7 @@ export function main() {
|
||||||
.then((_) => router.config({'path': '/a', 'component': DummyComponent}))
|
.then((_) => router.config({'path': '/a', 'component': DummyComponent}))
|
||||||
.then((_) => router.navigate('/a'))
|
.then((_) => router.navigate('/a'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
expect(outlet.spy('activate')).toHaveBeenCalled();
|
expect(outlet.spy('commit')).toHaveBeenCalled();
|
||||||
expect(location.urlChanges).toEqual(['/a']);
|
expect(location.urlChanges).toEqual(['/a']);
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -83,11 +83,11 @@ export function main() {
|
||||||
router.registerOutlet(outlet)
|
router.registerOutlet(outlet)
|
||||||
.then((_) => router.navigate('/a'))
|
.then((_) => router.navigate('/a'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
expect(outlet.spy('activate')).not.toHaveBeenCalled();
|
expect(outlet.spy('commit')).not.toHaveBeenCalled();
|
||||||
return router.config({'path': '/a', 'component': DummyComponent});
|
return router.config({'path': '/a', 'component': DummyComponent});
|
||||||
})
|
})
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
expect(outlet.spy('activate')).toHaveBeenCalled();
|
expect(outlet.spy('commit')).toHaveBeenCalled();
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -132,10 +132,10 @@ class DummyParentComp {
|
||||||
|
|
||||||
function makeDummyOutlet() {
|
function makeDummyOutlet() {
|
||||||
var ref = new DummyOutlet();
|
var ref = new DummyOutlet();
|
||||||
ref.spy('activate').andCallFake((_) => PromiseWrapper.resolve(true));
|
|
||||||
ref.spy('canActivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
ref.spy('canActivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
||||||
|
ref.spy('canReuse').andCallFake((_) => PromiseWrapper.resolve(false));
|
||||||
ref.spy('canDeactivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
ref.spy('canDeactivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
||||||
ref.spy('deactivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
ref.spy('commit').andCallFake((_) => PromiseWrapper.resolve(true));
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue