-
-
Save martinnormark/a27f8b7eecb89904fbf8 to your computer and use it in GitHub Desktop.
Angular 2 Persistent Router
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
import * as hookMod from 'angular2/src/router/lifecycle/lifecycle_annotations'; | |
import * as routerMod from 'angular2/src/router/router'; | |
import {isBlank, isPresent} from 'angular2/src/facade/lang'; | |
import {StringMapWrapper} from 'angular2/src/facade/collection'; | |
import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; | |
import {BaseException} from 'angular2/src/facade/exceptions'; | |
import { | |
ElementRef, DynamicComponentLoader, Directive, Injector, provide, ComponentRef, Attribute | |
} from 'angular2/core'; | |
import { | |
ComponentInstruction, CanReuse, OnReuse, CanDeactivate, | |
RouterOutlet, OnActivate, Router, RouteData, RouteParams, OnDeactivate | |
} from 'angular2/router'; | |
import {hasLifecycleHook} from 'angular2/src/router/lifecycle/route_lifecycle_reflector'; | |
/** | |
* Reference Cache Entry | |
*/ | |
class RefCacheItem { | |
constructor(public componentRef: ComponentRef) { | |
} | |
} | |
/** | |
* Reference Cache | |
*/ | |
class RefCache { | |
private cache: any = {}; | |
public getRef(type: any) { | |
return this.cache[type]; | |
} | |
public addRef(type: any, ref: RefCacheItem) { | |
this.cache[type] = ref; | |
} | |
public hasRef(type: any): boolean { | |
return !isBlank(this.cache[type]); | |
} | |
} | |
/** | |
* An outlet that persists the child views and re-uses their components. | |
* | |
* @author Wael Jammal | |
*/ | |
@Directive({ selector: 'persistent-router-outlet' }) | |
export class PersistentRouterOutlet extends RouterOutlet { | |
private currentInstruction: ComponentInstruction; | |
private currentElementRef; | |
private refCache: RefCache = new RefCache(); | |
private resolveToTrue = PromiseWrapper.resolve(true); | |
private currentComponentRef: ComponentRef; | |
constructor(elementRef: ElementRef, | |
private loader: DynamicComponentLoader, | |
private parentRouter: Router, | |
@Attribute('name') nameAttr: string) { | |
super(elementRef, loader, parentRouter, nameAttr); | |
this.currentElementRef = elementRef; | |
} | |
/** | |
* Called by the Router to instantiate a new component during the commit phase of a navigation. | |
* This method in turn is responsible for calling the `routerOnActivate` hook of its child. | |
*/ | |
public activate(nextInstruction: ComponentInstruction): Promise<any> { | |
let previousInstruction = this.currentInstruction; | |
this.currentInstruction = nextInstruction; | |
if (!this.refCache.hasRef(nextInstruction.urlPath)) { | |
let componentType = nextInstruction.componentType; | |
let childRouter = this.parentRouter.childRouter(componentType); | |
let providers = Injector.resolve([ | |
provide(RouteData, { useValue: nextInstruction.routeData }), | |
provide(RouteParams, { useValue: new RouteParams(nextInstruction.params) }), | |
provide(routerMod.Router, { useValue: childRouter }) | |
]); | |
return this.loader.loadNextToLocation(componentType, this.currentElementRef, providers) | |
.then((componentRef) => { | |
this.refCache.addRef(nextInstruction.urlPath, new RefCacheItem(componentRef)); | |
this.currentComponentRef = componentRef; | |
if (hasLifecycleHook(hookMod.routerOnActivate, componentType)) { | |
return (<OnActivate>componentRef.instance) | |
.routerOnActivate(nextInstruction, previousInstruction); | |
} | |
}); | |
} | |
else { | |
let ref = this.refCache.getRef(nextInstruction.urlPath); | |
ref.componentRef.location.nativeElement.hidden = false; | |
this.currentComponentRef = ref.componentRef; | |
return PromiseWrapper.resolve( | |
hasLifecycleHook(hookMod.routerOnReuse, this.currentInstruction.componentType) ? | |
(<OnReuse>ref.componentRef.instance).routerOnReuse(nextInstruction, previousInstruction) : true | |
); | |
} | |
} | |
/** | |
* Called by the Router during the commit phase of a navigation when an outlet | |
* reuses a component between different routes. | |
* This method in turn is responsible for calling the `routerOnReuse` hook of its child. | |
*/ | |
public reuse(nextInstruction: ComponentInstruction): Promise<any> { | |
let previousInstruction = this.currentInstruction; | |
this.currentInstruction = nextInstruction; | |
if (isBlank(this.currentComponentRef)) { | |
throw new BaseException(`Cannot reuse an outlet that does not contain a component.`); | |
} | |
let ref = this.refCache.getRef(nextInstruction.urlPath); | |
let currentRef = ref ? ref.componentRef : null; | |
return PromiseWrapper.resolve( | |
hasLifecycleHook(hookMod.routerOnReuse, this.currentInstruction.componentType) ? | |
(<OnReuse>currentRef.instance).routerOnReuse(nextInstruction, previousInstruction) : true | |
); | |
} | |
/** | |
* Called by the Router when an outlet disposes of a component's contents. | |
* This method in turn is responsible for calling the `routerOnDeactivate` hook of its child. | |
*/ | |
public deactivate(nextInstruction: ComponentInstruction): Promise<any> { | |
let next = this.resolveToTrue; | |
let ref = this.currentComponentRef; | |
if (isPresent(ref) && isPresent(this.currentInstruction) && | |
hasLifecycleHook(hookMod.routerOnDeactivate, this.currentInstruction.componentType)) { | |
next = PromiseWrapper.resolve( | |
(<OnDeactivate>ref.instance) | |
.routerOnDeactivate(nextInstruction, this.currentInstruction)); | |
} | |
return next.then(() => { | |
if (isPresent(ref)) { | |
ref.location.nativeElement.hidden = true; | |
} | |
}); | |
} | |
/** | |
* Called by the Router during recognition phase of a navigation. | |
* | |
* If this resolves to `false`, the given navigation is cancelled. | |
* | |
* This method delegates to the child component's `routerCanDeactivate` hook if it exists, | |
* and otherwise resolves to true. | |
*/ | |
public routerCanDeactivate(nextInstruction: ComponentInstruction): Promise<boolean> { | |
if (isBlank(this.currentInstruction)) { | |
return this.resolveToTrue; | |
} | |
let ref = this.currentComponentRef; | |
if (!ref) { | |
let foundRef = this.refCache.getRef(this.currentInstruction.urlPath); | |
ref = foundRef ? foundRef.componentRef : null; | |
} | |
if (hasLifecycleHook(hookMod.routerCanDeactivate, this.currentInstruction.componentType)) { | |
return PromiseWrapper.resolve( | |
(<CanDeactivate>ref.instance) | |
.routerCanDeactivate(nextInstruction, this.currentInstruction)); | |
} | |
return this.resolveToTrue; | |
} | |
/** | |
* Called by the Router during recognition phase of a navigation. | |
* | |
* If the new child component has a different Type than the existing child component, | |
* this will resolve to `false`. You can't reuse an old component when the new component | |
* is of a different Type. | |
* | |
* Otherwise, this method delegates to the child component's `routerCanReuse` hook if it exists, | |
* or resolves to true if the hook is not present. | |
*/ | |
public routerCanReuse(nextInstruction: ComponentInstruction): Promise<boolean> { | |
let result; | |
let ref = this.currentComponentRef; | |
if (!ref) { | |
let foundRef = this.refCache.getRef(nextInstruction.urlPath); | |
ref = foundRef ? foundRef.componentRef : null; | |
} | |
if (isBlank(this.currentInstruction) || !this.refCache.hasRef(nextInstruction.urlPath) || this.currentInstruction.componentType !== nextInstruction.componentType) { | |
result = false; | |
} else if (hasLifecycleHook(hookMod.routerCanReuse, this.currentInstruction.componentType)) { | |
result = (<CanReuse>ref.instance) | |
.routerCanReuse(nextInstruction, this.currentInstruction); | |
} else { | |
result = nextInstruction === this.currentInstruction || | |
(isPresent(nextInstruction.params) && isPresent(this.currentInstruction.params) && | |
StringMapWrapper.equals(nextInstruction.params, this.currentInstruction.params)); | |
} | |
return PromiseWrapper.resolve(result); | |
} | |
} |
Also learnt some typescript sophistry from your post:
private cache: any = {};
cache can be used as a Hashmap:
this.cache[type] = ref;
type any can be used as a map - just so different. No need for Collections.Dictionary
Thanks.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This will cache the component using the
urlPath
property of theComponentInstruction
type.In the component types that needs to support reuse, implement the
CanReuse
interface as follows: