Last active
August 30, 2017 11:05
-
-
Save Toxicable/e60535e9e6cc69bd70dafd06f37c6dee to your computer and use it in GitHub Desktop.
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
//usage | |
const MY_STORE_TOKEN = new InjectionToken('MY_STORE_TOKEN'); | |
@NgModule({ | |
providers: [ | |
//on the ServerModule you want to provide | |
//ServerStateTransferModule | |
BrowserStateTransferModule, | |
//helper function which will provide the store accosiated with this token | |
//makes it a little easier to use | |
provideStore(MY_STORE_TOKEN, 'my-store') | |
] | |
}) | |
export class AppModule{} | |
@Component({}) | |
export class Component{ | |
constructor( | |
//you can inject that token you provided if you provided to get instant access to the store | |
@Inject(MY_STORE_TOKEN) private store: StateTransferStore<{person: string, age: number}>, | |
//or you can inject the store manager to get access to the underlying manager | |
private storeManager: StateTransferStoreManager, | |
){ | |
//this is how you might use the manager | |
var myStore = this.storeManager.get('my-store'); | |
//using the store | |
this.store.get('person'); | |
this.store.set('name', 3); | |
} | |
} | |
//store.ts | |
export interface Store<V> { | |
get<K extends keyof V>(key: K): V[K]; | |
set<K extends keyof V>(key: K, value: V[K]): V[K]; | |
has<K extends keyof V>(key: K): boolean; | |
delete<K extends keyof V>(key: K): void; | |
} | |
//state-store.ts | |
import { Store } from "./store"; | |
export class StateTransferStore<V extends Object = any> implements Store<V>{ | |
constructor(private store: V) { | |
} | |
public get<K extends keyof V>(key: K): V[K] { | |
return this.store[key]; | |
} | |
public set<K extends keyof V>(key: K, value: V[K]): V[K] { | |
this.store[key] = value; | |
return this.store[key]; | |
} | |
public has<K extends keyof V>(key: K): boolean { | |
const value = this.store[key]; | |
return !!value; | |
} | |
public delete<K extends keyof V>(key: K): V[K] { | |
const value = this.store[key]; | |
delete this.store[key]; | |
return value; | |
} | |
} | |
//store-manager.ts | |
import { Inject, Injectable, APP_ID, RendererFactory2, ViewEncapsulation } from '@angular/core'; | |
import { PlatformState } from "@angular/platform-server"; | |
import { StateTransferStore } from "./state-store"; | |
type StateTransferWindow = { [key: string]: string } & Window | |
const STATE_TRANSFER_TOKEN = 'STATE_TRANSFER_TOKEN' | |
@Injectable() | |
export class StateTransferStoreManager { | |
protected stores: { [key: string]: StateTransferStore } = {}; | |
public get<T extends object>(key: string): StateTransferStore<T> { | |
const store = this.stores[key]; | |
if (store) { | |
// existing store | |
return store; | |
} else { | |
// new store initalized to empty | |
return new StateTransferStore<T>({} as T); | |
} | |
} | |
inject() { }; | |
} | |
export class ServerStateTransferStoreManager extends StateTransferStoreManager { | |
constructor( | |
private state: PlatformState, | |
private rendererFactory: RendererFactory2, | |
@Inject(APP_ID) private appId: string, | |
) { | |
super(); | |
const stores = JSON.parse((window as StateTransferWindow)[`${this.appId}-${STATE_TRANSFER_TOKEN}`]); | |
Object.keys(stores).forEach(key => { | |
this.stores[key] = new StateTransferStore(stores[key]); | |
}) | |
} | |
inject() { | |
const document: any = this.state.getDocument(); | |
const transferStateString = JSON.stringify(this.stores); | |
const renderer = this.rendererFactory.createRenderer(document, { | |
id: '-1', | |
encapsulation: ViewEncapsulation.None, | |
styles: [], | |
data: {} | |
}); | |
const head = document.head; | |
if (!head) { | |
throw new Error('Please have <head> as the first element in your document'); | |
} | |
const script = renderer.createElement('script'); | |
renderer.setValue(script, `window['${this.appId}-${STATE_TRANSFER_TOKEN}']=${transferStateString}`); | |
renderer.appendChild(head, script); | |
} | |
} | |
//browser-module.ts | |
import { StateTransferStoreManager } from "./store-manager"; | |
import { NgModule } from "@angular/core"; | |
let browserManager: StateTransferStoreManager; | |
export function browserStateTransferStoreManagerFactory() { | |
if (!browserManager) { | |
browserManager = new StateTransferStoreManager(); | |
} | |
return browserManager | |
} | |
@NgModule({ | |
providers: [ | |
StateTransferStoreManager, | |
{ | |
provide: StateTransferStoreManager, | |
useFactory: browserStateTransferStoreManagerFactory | |
} | |
] | |
}) | |
export class BrowserStateTransferModule { } | |
//server-module.ts | |
import { ApplicationRef, RendererFactory2, NgModule, APP_BOOTSTRAP_LISTENER, APP_ID } from "@angular/core"; | |
import { StateTransferStoreManager, ServerStateTransferStoreManager } from "./store-manager"; | |
import { PlatformState } from "@angular/platform-server"; | |
let serverManager: ServerStateTransferStoreManager; | |
export function serverStateTransferStoreManagerFactory( | |
state: PlatformState, | |
rendererFactory: RendererFactory2, | |
appId: string, | |
) { | |
if (!serverManager) { | |
serverManager = new ServerStateTransferStoreManager(state, rendererFactory, appId); | |
} | |
return serverManager; | |
} | |
export function stateTransferOnBootsrap(appRef: ApplicationRef, transferState: StateTransferStoreManager) { | |
return () => { | |
appRef.isStable | |
.filter(stable => stable) | |
.first() | |
.subscribe(() => { | |
transferState.inject(); | |
}); | |
}; | |
} | |
@NgModule({ | |
providers: [ | |
{ | |
provide: StateTransferStoreManager, | |
useFactory: serverStateTransferStoreManagerFactory, | |
deps: [ | |
PlatformState, | |
RendererFactory2, | |
APP_ID | |
] | |
}, | |
{ | |
provide: APP_BOOTSTRAP_LISTENER, | |
useFactory: stateTransferOnBootsrap, | |
multi: true, | |
deps: [ | |
ApplicationRef, | |
StateTransferStoreManager | |
] | |
} | |
] | |
}) | |
export class ServerStateTransferModule { } | |
//store-factory.ts | |
import { StateTransferStoreManager } from "./store-manager"; | |
import { StateTransferStore } from "./state-store"; | |
import { Provider } from "@angular/core"; | |
export function provideStoreFactory(name: string, manager: StateTransferStoreManager): StateTransferStore { | |
return manager.get(name); | |
} | |
export function provideStore(storeToken: any, storeName: string): Provider[] { | |
return [ | |
{ | |
provide: storeName, useValue: storeName | |
}, | |
{ | |
provide: storeToken, | |
useFactory: provideStoreFactory, | |
deps: [ | |
name, | |
StateTransferStoreManager | |
] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment