Skip to content

Instantly share code, notes, and snippets.

@Toxicable
Last active August 30, 2017 11:05
Show Gist options
  • Save Toxicable/e60535e9e6cc69bd70dafd06f37c6dee to your computer and use it in GitHub Desktop.
Save Toxicable/e60535e9e6cc69bd70dafd06f37c6dee to your computer and use it in GitHub Desktop.
//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