Switched to angular 5's built-in transfer state api

This commit is contained in:
Art Lowel
2018-02-01 13:17:59 +01:00
parent 1aebb4fb5b
commit 4fbceab1b9
21 changed files with 159 additions and 398 deletions

View File

@@ -1,30 +1,19 @@
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';
import { IdlePreload, IdlePreloadModule } from 'angular-idle-preload';
import { EffectsModule } from '@ngrx/effects';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { IdlePreload, IdlePreloadModule } from 'angular-idle-preload';
import { AppComponent } from '../../app/app.component';
import { AppModule } from '../../app/app.module';
import { BrowserTransferStateModule } from '../transfer-state/browser-transfer-state.module';
import { TransferState } from '../transfer-state/transfer-state';
import { BrowserTransferStoreEffects } from '../transfer-store/browser-transfer-store.effects';
import { BrowserTransferStoreModule } from '../transfer-store/browser-transfer-store.module';
export function init(cache: TransferState) {
return () => {
cache.initialize();
};
}
import { DSpaceBrowserTransferStateModule } from '../transfer-state/dspace-browser-transfer-state.module';
import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service';
export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
@@ -34,7 +23,7 @@ export function createTranslateLoader(http: HttpClient) {
bootstrap: [AppComponent],
imports: [
BrowserModule.withServerTransition({
appId: 'ds-app-id'
appId: 'dspace-angular'
}),
HttpClientModule,
// forRoot ensures the providers are only created once
@@ -46,8 +35,7 @@ export function createTranslateLoader(http: HttpClient) {
IdlePreload
}),
BrowserAnimationsModule,
BrowserTransferStateModule,
BrowserTransferStoreModule,
DSpaceBrowserTransferStateModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
@@ -55,20 +43,13 @@ export function createTranslateLoader(http: HttpClient) {
deps: [HttpClient]
}
}),
EffectsModule.forRoot([BrowserTransferStoreEffects]),
AppModule
],
providers: [
{
provide: APP_INITIALIZER,
multi: true,
useFactory: init,
deps: [
TransferState
]
}
]
})
export class BrowserAppModule {
constructor(
private transferState: DSpaceTransferState,
) {
this.transferState.transfer();
}
}

View File

@@ -1,53 +1,20 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ServerModule } from '@angular/platform-server';
import { RouterModule } from '@angular/router';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/first';
import { ApplicationRef, NgModule, APP_BOOTSTRAP_LISTENER } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ServerModule } from '@angular/platform-server';
import { BrowserModule } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Request } from 'express';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { Store } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { TranslateUniversalLoader } from '../translate-universal-loader';
import { ServerTransferStateModule } from '../transfer-state/server-transfer-state.module';
import { TransferState } from '../transfer-state/transfer-state';
import { ServerTransferStoreEffects } from '../transfer-store/server-transfer-store.effects';
import { ServerTransferStoreModule } from '../transfer-store/server-transfer-store.module';
import { AppState } from '../../app/app.reducer';
import { AppModule } from '../../app/app.module';
import { AppComponent } from '../../app/app.component';
import { GLOBAL_CONFIG, GlobalConfig } from '../../config';
import { AppModule } from '../../app/app.module';
import { DSpaceServerTransferStateModule } from '../transfer-state/dspace-server-transfer-state.module';
import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service';
export function boot(cache: TransferState, appRef: ApplicationRef, store: Store<AppState>, request: Request, config: GlobalConfig) {
// authentication mechanism goes here
return () => {
appRef.isStable.filter((stable: boolean) => stable).first().subscribe(() => {
// isStable == true doesn't guarantee that all dispatched actions have been
// processed yet. So in those cases the store snapshot wouldn't be complete
// and a rehydrate would leave the app in a broken state
//
// This setTimeout without delay schedules the cache.inject() to happen ASAP
// after everything that's already scheduled, and it solves that problem.
setTimeout(() => {
cache.inject();
}, 0);
});
};
}
import { TranslateUniversalLoader } from '../translate-universal-loader';
export function createTranslateLoader() {
return new TranslateUniversalLoader('dist/assets/i18n/', '.json');
@@ -57,14 +24,13 @@ export function createTranslateLoader() {
bootstrap: [AppComponent],
imports: [
BrowserModule.withServerTransition({
appId: 'ds-app-id'
appId: 'dspace-angular'
}),
RouterModule.forRoot([], {
useHash: false
}),
NoopAnimationsModule,
ServerTransferStateModule,
ServerTransferStoreModule,
DSpaceServerTransferStateModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
@@ -72,25 +38,16 @@ export function createTranslateLoader() {
deps: []
}
}),
EffectsModule.forRoot([ServerTransferStoreEffects]),
ServerModule,
AppModule
],
providers: [
{
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
useFactory: boot,
deps: [
TransferState,
ApplicationRef,
Store,
REQUEST,
GLOBAL_CONFIG
]
}
]
})
export class ServerAppModule {
constructor(
private transferState: DSpaceTransferState,
) {
this.transferState.transfer();
}
}

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { BrowserTransferState } from './browser-transfer-state';
import { TransferState } from './transfer-state';
@NgModule({
providers: [
{ provide: TransferState, useClass: BrowserTransferState }
]
})
export class BrowserTransferStateModule {
}

View File

@@ -1,47 +0,0 @@
import { Inject, Injectable } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { TransferState } from './transfer-state';
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
import { AppState } from '../../app/app.reducer';
import { GLOBAL_CONFIG, GlobalConfig } from '../../config';
import { RouterNavigationAction } from '@ngrx/router-store';
@Injectable()
export class BrowserTransferState extends TransferState {
constructor(private store: Store<AppState>, @Inject(GLOBAL_CONFIG) private config: GlobalConfig) {
super();
}
initialize() {
// tslint:disable-next-line:no-string-literal
const cache: any = window['TRANSFER_STATE'] || {};
Object.keys(cache).forEach((key: string) => {
if (key !== 'actions') {
this.set(key, cache[key]);
}
});
if (this.config.prerenderStrategy === 'replay') {
if (cache.actions !== undefined) {
if (this.config.debug) {
console.info('Replay:', (cache.actions !== undefined && cache.actions !== null) ? cache.actions : []);
}
this.store.dispatch(new StoreAction(StoreActionTypes.REPLAY, cache.actions));
} else {
console.info('No actions occured during prerender.');
}
} else if (this.config.prerenderStrategy === 'rehydrate') {
if (this.config.debug) {
console.info('Rehydrate:', (cache.state !== undefined && cache.state !== null) ? cache.state : []);
}
this.store.dispatch(new StoreAction(StoreActionTypes.REHYDRATE, cache.state));
} else {
console.warn([this.config.prerenderStrategy, 'is not a valid prerender strategy!'].join(' '));
}
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { BrowserTransferStateModule } from '@angular/platform-browser';
import { DSpaceBrowserTransferState } from './dspace-browser-transfer-state.service';
import { DSpaceTransferState } from './dspace-transfer-state.service';
@NgModule({
imports: [
BrowserTransferStateModule
],
providers: [
{ provide: DSpaceTransferState, useClass: DSpaceBrowserTransferState }
]
})
export class DSpaceBrowserTransferStateModule {
}

View File

@@ -0,0 +1,12 @@
import { Injectable } from '@angular/core';
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
import { DSpaceTransferState } from './dspace-transfer-state.service';
@Injectable()
export class DSpaceBrowserTransferState extends DSpaceTransferState {
transfer() {
const state = this.transferState.get<any>(DSpaceTransferState.NGRX_STATE, null);
this.transferState.remove(DSpaceTransferState.NGRX_STATE);
this.store.dispatch(new StoreAction(StoreActionTypes.REHYDRATE, state));
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { ServerTransferStateModule } from '@angular/platform-server';
import { DSpaceServerTransferState } from './dspace-server-transfer-state.service';
import { DSpaceTransferState } from './dspace-transfer-state.service';
@NgModule({
imports: [
ServerTransferStateModule
],
providers: [
{ provide: DSpaceTransferState, useClass: DSpaceServerTransferState }
]
})
export class DSpaceServerTransferStateModule {
}

View File

@@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { DSpaceTransferState } from './dspace-transfer-state.service';
@Injectable()
export class DSpaceServerTransferState extends DSpaceTransferState {
transfer() {
this.transferState.onSerialize(DSpaceTransferState.NGRX_STATE, () => {
let state;
this.store.take(1).subscribe((saveState: any) => {
state = saveState;
});
return state;
});
}
}

View File

@@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { makeStateKey, TransferState } from '@angular/platform-browser';
import { Store } from '@ngrx/store';
import { AppState } from '../../app/app.reducer';
@Injectable()
export abstract class DSpaceTransferState {
protected static NGRX_STATE = makeStateKey('NGRX_STATE');
constructor(
protected transferState: TransferState,
protected store: Store<AppState>
) {
}
abstract transfer(): void
}

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { ServerTransferState } from './server-transfer-state';
import { TransferState } from './transfer-state';
@NgModule({
providers: [
{ provide: TransferState, useClass: ServerTransferState }
]
})
export class ServerTransferStateModule {
}

View File

@@ -1,37 +0,0 @@
import { Inject, Injectable, RendererFactory2, ViewEncapsulation } from '@angular/core';
import { INITIAL_CONFIG, PlatformState } from '@angular/platform-server';
import { TransferState } from './transfer-state';
@Injectable()
export class ServerTransferState extends TransferState {
constructor(private state: PlatformState, private rendererFactory: RendererFactory2) {
super();
}
inject() {
try {
const document: any = this.state.getDocument();
const transferStateString = JSON.stringify(this.toJson());
const renderer = this.rendererFactory.createRenderer(document, {
id: '-1',
encapsulation: ViewEncapsulation.None,
styles: [],
data: {}
});
const head = document.children[1].children[0];
if (head.name !== 'head') {
throw new Error('Please have <head> as the first element in your document');
}
const script = renderer.createElement('script');
renderer.setValue(script, `window['TRANSFER_STATE'] = ${transferStateString}`);
renderer.appendChild(head, script);
} catch (e) {
console.error(e);
}
}
}

View File

@@ -1,40 +0,0 @@
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Store } from '@ngrx/store';
@Injectable()
export class TransferState {
protected map = new Map<string, any>();
keys() {
return this.map.keys();
}
get(key: string): any {
return this.map.get(key);
}
set(key: string, value: any): Map<string, any> {
return this.map.set(key, value);
}
toJson(): any {
const json: any = {};
Array.from(this.keys())
.forEach((key: string) => {
json[key] = this.get(key);
});
return json;
}
initialize(): void {
console.log('Initialize does nothing!');
}
inject(): void {
console.log('Inject does nothing!');
}
}

View File

@@ -1,28 +0,0 @@
import { Inject, Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Effect, Actions } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { types } from '../../app/shared/ngrx/type';
import { TransferStoreEffects } from './transfer-store.effects';
import { GLOBAL_CONFIG, GlobalConfig } from '../../config';
@Injectable()
export class BrowserTransferStoreEffects extends TransferStoreEffects {
@Effect({ dispatch: false }) log = this.actions.ofType(...types()).switchMap((action: Action) => {
if (this.config.debug) {
console.info(action);
}
return Observable.of({});
});
constructor(private actions: Actions, @Inject(GLOBAL_CONFIG) public config: GlobalConfig) {
super();
}
}

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { BrowserTransferStoreEffects } from './browser-transfer-store.effects';
import { TransferStoreEffects } from './transfer-store.effects';
@NgModule({
providers: [
{ provide: TransferStoreEffects, useClass: BrowserTransferStoreEffects }
]
})
export class BrowserTransferStoreModule {
}

View File

@@ -1,36 +0,0 @@
import { Inject, Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Effect, Actions } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { types } from '../../app/shared/ngrx/type';
import { TransferStoreEffects } from './transfer-store.effects';
import { TransferState } from '../transfer-state/transfer-state';
import { GLOBAL_CONFIG, GlobalConfig } from '../../config';
@Injectable()
export class ServerTransferStoreEffects extends TransferStoreEffects {
@Effect({ dispatch: false }) track = this.actions.ofType(...types()).switchMap((action: Action) => {
this.cacheAction(action);
return Observable.of({});
});
constructor(private actions: Actions, private cache: TransferState, @Inject(GLOBAL_CONFIG) public config: GlobalConfig) {
super();
this.cache.set('actions', new Array<Action>());
}
private cacheAction(action: Action): void {
if (this.config.debug) {
console.info('Cache:', action);
}
this.cache.get('actions').push(action);
}
}

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { ServerTransferStoreEffects } from './server-transfer-store.effects';
import { TransferStoreEffects } from './transfer-store.effects';
@NgModule({
providers: [
{ provide: TransferStoreEffects, useClass: ServerTransferStoreEffects }
]
})
export class ServerTransferStoreModule {
}

View File

@@ -1,3 +0,0 @@
export abstract class TransferStoreEffects {
}