-
-
Save danrasmuson/89cde9ce22d89167cec6d7f9a9240558 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
import * as hookMod from 'angular2/src/router/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/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]); | |
} | |
public removeRef(type: any){ | |
delete this.cache[type]; | |
} | |
public remove(ref){ | |
// remove self from cache | |
this.removeRef(ref.componentType); | |
// remove children | |
var parent = ref.location.nativeElement; | |
Object.keys(this.cache).forEach((key)=>{ | |
var child = this.cache[key].componentRef.location.nativeElement; | |
if (parent.contains(child)){ | |
this.removeRef(key) | |
} | |
}) | |
// will destroy component and all children | |
// sense this is an angular call, all lifecycle methods will be called | |
ref.dispose(); | |
} | |
} | |
const refCache = new RefCache(); | |
/** | |
* 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 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 (!refCache.hasRef(nextInstruction.componentType)) { | |
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) => { | |
refCache.addRef(nextInstruction.componentType, new RefCacheItem(componentRef)); | |
this.currentComponentRef = componentRef; | |
if (hasLifecycleHook(hookMod.routerOnActivate, componentType)) { | |
return (<OnActivate>componentRef.instance) | |
.routerOnActivate(nextInstruction, previousInstruction); | |
} | |
}); | |
} | |
else { | |
let ref = refCache.getRef(nextInstruction.componentType); | |
ref.componentRef.location.nativeElement.style.display = 'initial'; | |
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 = refCache.getRef(nextInstruction.componentType); | |
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)) { | |
if (ref.instance.routerCanReuse && ref.instance.routerCanReuse(nextInstruction, ref.componentType)){ | |
ref.location.nativeElement.style.display = 'none'; | |
} else{ | |
refCache.remove(ref); | |
} | |
} | |
}); | |
} | |
/** | |
* 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 = refCache.getRef(this.currentInstruction.componentType); | |
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 = refCache.getRef(nextInstruction.componentType); | |
ref = foundRef ? foundRef.componentRef : null; | |
} | |
if (isBlank(this.currentInstruction) || | |
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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, does this work with the latest Angular version 4?