Providing global config as an opaque token.

This commit is contained in:
William Welling
2017-03-24 16:15:01 -05:00
parent 76b1875e54
commit 99f936ff45
10 changed files with 74 additions and 27 deletions

View File

@@ -13,7 +13,7 @@ module.exports = {
}, },
"cache": { "cache": {
// how long should objects be cached for by default // how long should objects be cached for by default
"msToLive": 15 * 60 * 1000, //15 minutes "msToLive": 15 * 60 * 1000, //15 minutes
}, },
"universal": { "universal": {
//on the client: start with the state on the server //on the client: start with the state on the server

View File

@@ -1,6 +1,7 @@
import { import {
Component, Component,
ChangeDetectionStrategy, ChangeDetectionStrategy,
Inject,
ViewEncapsulation, ViewEncapsulation,
OnDestroy, OnDestroy,
OnInit, HostListener OnInit, HostListener
@@ -9,7 +10,8 @@ import { TranslateService } from "ng2-translate";
import { HostWindowState } from "./shared/host-window.reducer"; import { HostWindowState } from "./shared/host-window.reducer";
import { Store } from "@ngrx/store"; import { Store } from "@ngrx/store";
import { HostWindowResizeAction } from "./shared/host-window.actions"; import { HostWindowResizeAction } from "./shared/host-window.actions";
import { GlobalConfig } from "../config";
import { GLOBAL_CONFIG, GlobalConfig, globalConfig } from '../config';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.Default, changeDetection: ChangeDetectionStrategy.Default,
@@ -28,13 +30,14 @@ export class AppComponent implements OnDestroy, OnInit {
recipient: 'World' recipient: 'World'
}; };
env: string = GlobalConfig.production; env: string = this.config.production;
styles = { styles = {
color: 'red' color: 'red'
}; };
constructor( constructor(
@Inject(GLOBAL_CONFIG) private config: GlobalConfig,
private translate: TranslateService, private translate: TranslateService,
private store: Store<HostWindowState> private store: Store<HostWindowState>
) { ) {

View File

@@ -1,4 +1,4 @@
import { Injectable } from "@angular/core"; import { Inject, Injectable } from "@angular/core";
import { DataEffects } from "./data.effects"; import { DataEffects } from "./data.effects";
import { Serializer } from "../serializer"; import { Serializer } from "../serializer";
import { Collection } from "../shared/collection.model"; import { Collection } from "../shared/collection.model";
@@ -9,15 +9,18 @@ import { Actions, Effect } from "@ngrx/effects";
import { RequestCacheFindAllAction, RequestCacheFindByIDAction } from "../cache/request-cache.actions"; import { RequestCacheFindAllAction, RequestCacheFindByIDAction } from "../cache/request-cache.actions";
import { CollectionDataService } from "./collection-data.service"; import { CollectionDataService } from "./collection-data.service";
import { GLOBAL_CONFIG, GlobalConfig, globalConfig } from '../../../config';
@Injectable() @Injectable()
export class CollectionDataEffects extends DataEffects<Collection> { export class CollectionDataEffects extends DataEffects<Collection> {
constructor( constructor(
@Inject(GLOBAL_CONFIG) config: GlobalConfig,
actions$: Actions, actions$: Actions,
restApi: DSpaceRESTv2Service, restApi: DSpaceRESTv2Service,
cache: ObjectCacheService, cache: ObjectCacheService,
dataService: CollectionDataService dataService: CollectionDataService
) { ) {
super(actions$, restApi, cache, dataService); super(config, actions$, restApi, cache, dataService);
} }
protected getFindAllEndpoint(action: RequestCacheFindAllAction): string { protected getFindAllEndpoint(action: RequestCacheFindAllAction): string {

View File

@@ -1,9 +1,9 @@
import { Inject } from "@angular/core";
import { Actions } from "@ngrx/effects"; import { Actions } from "@ngrx/effects";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { DSpaceRESTV2Response } from "../dspace-rest-v2/dspace-rest-v2-response.model"; import { DSpaceRESTV2Response } from "../dspace-rest-v2/dspace-rest-v2-response.model";
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service"; import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
import { ObjectCacheService } from "../cache/object-cache.service"; import { ObjectCacheService } from "../cache/object-cache.service";
import { GlobalConfig } from "../../../config";
import { CacheableObject } from "../cache/object-cache.reducer"; import { CacheableObject } from "../cache/object-cache.reducer";
import { Serializer } from "../serializer"; import { Serializer } from "../serializer";
import { import {
@@ -13,17 +13,20 @@ import {
import { DataService } from "./data.service"; import { DataService } from "./data.service";
import { hasNoValue } from "../../shared/empty.util"; import { hasNoValue } from "../../shared/empty.util";
import { GlobalConfig } from '../../../config';
export abstract class DataEffects<T extends CacheableObject> { export abstract class DataEffects<T extends CacheableObject> {
protected abstract getFindAllEndpoint(action: RequestCacheFindAllAction): string; protected abstract getFindAllEndpoint(action: RequestCacheFindAllAction): string;
protected abstract getFindByIdEndpoint(action: RequestCacheFindByIDAction): string; protected abstract getFindByIdEndpoint(action: RequestCacheFindByIDAction): string;
protected abstract getSerializer(): Serializer<T>; protected abstract getSerializer(): Serializer<T>;
constructor( constructor(
private config: GlobalConfig,
private actions$: Actions, private actions$: Actions,
private restApi: DSpaceRESTv2Service, private restApi: DSpaceRESTv2Service,
private objectCache: ObjectCacheService, private objectCache: ObjectCacheService,
private dataService: DataService<T> private dataService: DataService<T>
) {} ) { }
// TODO, results of a findall aren't retrieved from cache yet // TODO, results of a findall aren't retrieved from cache yet
protected findAll = this.actions$ protected findAll = this.actions$
@@ -38,11 +41,11 @@ export abstract class DataEffects<T extends CacheableObject> {
if (hasNoValue(t) || hasNoValue(t.uuid)) { if (hasNoValue(t) || hasNoValue(t.uuid)) {
throw new Error('The server returned an invalid object'); throw new Error('The server returned an invalid object');
} }
this.objectCache.add(t, GlobalConfig.cache.msToLive); this.objectCache.add(t, this.config.cache.msToLive);
}); });
}) })
.map((ts: Array<T>) => ts.map(t => t.uuid)) .map((ts: Array<T>) => ts.map(t => t.uuid))
.map((ids: Array<string>) => new RequestCacheSuccessAction(action.payload.key, ids, new Date().getTime(), GlobalConfig.cache.msToLive)) .map((ids: Array<string>) => new RequestCacheSuccessAction(action.payload.key, ids, new Date().getTime(), this.config.cache.msToLive))
.catch((error: Error) => Observable.of(new RequestCacheErrorAction(action.payload.key, error.message))); .catch((error: Error) => Observable.of(new RequestCacheErrorAction(action.payload.key, error.message)));
}); });
@@ -56,9 +59,9 @@ export abstract class DataEffects<T extends CacheableObject> {
if (hasNoValue(t) || hasNoValue(t.uuid)) { if (hasNoValue(t) || hasNoValue(t.uuid)) {
throw new Error('The server returned an invalid object'); throw new Error('The server returned an invalid object');
} }
this.objectCache.add(t, GlobalConfig.cache.msToLive); this.objectCache.add(t, this.config.cache.msToLive);
}) })
.map((t: T) => new RequestCacheSuccessAction(action.payload.key, [t.uuid], new Date().getTime(), GlobalConfig.cache.msToLive)) .map((t: T) => new RequestCacheSuccessAction(action.payload.key, [t.uuid], new Date().getTime(), this.config.cache.msToLive))
.catch((error: Error) => Observable.of(new RequestCacheErrorAction(action.payload.key, error.message))); .catch((error: Error) => Observable.of(new RequestCacheErrorAction(action.payload.key, error.message)));
}); });

View File

@@ -1,4 +1,4 @@
import { Injectable } from "@angular/core"; import { Inject, Injectable } from "@angular/core";
import { DataEffects } from "./data.effects"; import { DataEffects } from "./data.effects";
import { Serializer } from "../serializer"; import { Serializer } from "../serializer";
import { Item } from "../shared/item.model"; import { Item } from "../shared/item.model";
@@ -9,15 +9,18 @@ import { Actions, Effect } from "@ngrx/effects";
import { RequestCacheFindAllAction, RequestCacheFindByIDAction } from "../cache/request-cache.actions"; import { RequestCacheFindAllAction, RequestCacheFindByIDAction } from "../cache/request-cache.actions";
import { ItemDataService } from "./item-data.service"; import { ItemDataService } from "./item-data.service";
import { GLOBAL_CONFIG, GlobalConfig, globalConfig } from '../../../config';
@Injectable() @Injectable()
export class ItemDataEffects extends DataEffects<Item> { export class ItemDataEffects extends DataEffects<Item> {
constructor( constructor(
@Inject(GLOBAL_CONFIG) config: GlobalConfig,
actions$: Actions, actions$: Actions,
restApi: DSpaceRESTv2Service, restApi: DSpaceRESTv2Service,
cache: ObjectCacheService, cache: ObjectCacheService,
dataService: ItemDataService dataService: ItemDataService
) { ) {
super(actions$, restApi, cache, dataService); super(config, actions$, restApi, cache, dataService);
} }
protected getFindAllEndpoint(action: RequestCacheFindAllAction): string { protected getFindAllEndpoint(action: RequestCacheFindAllAction): string {

View File

@@ -1,14 +1,16 @@
import { Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { Http, RequestOptionsArgs } from '@angular/http'; import { Http, RequestOptionsArgs } from '@angular/http';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { RESTURLCombiner } from "../url-combiner/rest-url-combiner"; import { RESTURLCombiner } from "../url-combiner/rest-url-combiner";
import { GLOBAL_CONFIG, GlobalConfig, globalConfig } from '../../../config';
/** /**
* Service to access DSpace's REST API * Service to access DSpace's REST API
*/ */
@Injectable() @Injectable()
export class DSpaceRESTv2Service { export class DSpaceRESTv2Service {
constructor(public _http: Http) { constructor(private http: Http, @Inject(GLOBAL_CONFIG) private config: GlobalConfig) {
} }
@@ -23,7 +25,7 @@ export class DSpaceRESTv2Service {
* An Observablse<string> containing the response from the server * An Observablse<string> containing the response from the server
*/ */
get(relativeURL: string, options?: RequestOptionsArgs): Observable<string> { get(relativeURL: string, options?: RequestOptionsArgs): Observable<string> {
return this._http.get(new RESTURLCombiner(relativeURL).toString(), options) return this.http.get(new RESTURLCombiner(this.config, relativeURL).toString(), options)
.map(res => res.json()) .map(res => res.json())
.catch(err => { .catch(err => {
console.log('Error: ', err); console.log('Error: ', err);

View File

@@ -1,5 +1,6 @@
import { URLCombiner } from "./url-combiner"; import { URLCombiner } from "./url-combiner";
import { GlobalConfig } from "../../../config";
import { GlobalConfig } from '../../../config';
/** /**
* Combines a variable number of strings representing parts * Combines a variable number of strings representing parts
@@ -7,8 +8,8 @@ import { GlobalConfig } from "../../../config";
* *
* TODO write tests once GlobalConfig becomes injectable * TODO write tests once GlobalConfig becomes injectable
*/ */
export class RESTURLCombiner extends URLCombiner{ export class RESTURLCombiner extends URLCombiner {
constructor(...parts:Array<string>) { constructor(config: GlobalConfig, ...parts: Array<string>) {
super(GlobalConfig.rest.baseURL, GlobalConfig.rest.nameSpace, ...parts); super(config.rest.baseURL, config.rest.nameSpace, ...parts);
} }
} }

View File

@@ -1,8 +1,10 @@
// Look in ./config folder for config // Look in ./config folder for config
import { OpaqueToken } from '@angular/core';
const path = require('path'); import path from 'path';
let configContext = require.context("../config", false, /js$/); let configContext = require.context("../config", false, /js$/);
let EnvConfig: any = {}; let EnvConfig: any = {};
let EnvConfigFile: string; let EnvConfigFile: string;
let DefaultConfig: any = {}; let DefaultConfig: any = {};
@@ -29,6 +31,29 @@ try {
EnvConfig = {}; EnvConfig = {};
} }
const GlobalConfig = Object.assign(DefaultConfig, EnvConfig); const GLOBAL_CONFIG = new OpaqueToken('config');
export {GlobalConfig} interface GlobalConfig {
"production": string,
"rest": {
"nameSpace": string,
"baseURL": string
},
"ui": {
"nameSpace": string,
"baseURL": string
},
"cache": {
"msToLive": number,
},
"universal": {
"shouldRehydrate": boolean
}
}
const globalConfig = {
provide: GLOBAL_CONFIG,
useValue: <GlobalConfig>Object.assign(DefaultConfig, EnvConfig)
};
export { GLOBAL_CONFIG, GlobalConfig, globalConfig}

View File

@@ -1,4 +1,4 @@
import { NgModule } from '@angular/core'; import { Inject, NgModule } from '@angular/core';
import { Http } from '@angular/http'; import { Http } from '@angular/http';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@@ -22,7 +22,8 @@ import { effects } from '../../app/app.effects';
// see https://github.com/angular/angular/pull/12322 // see https://github.com/angular/angular/pull/12322
import { Meta } from '../angular2-meta'; import { Meta } from '../angular2-meta';
import { RehydrateStoreAction } from "../../app/store.actions"; import { RehydrateStoreAction } from "../../app/store.actions";
import { GlobalConfig } from "../../config";
import { GLOBAL_CONFIG, GlobalConfig, globalConfig } from '../../config';
// import * as LRU from 'modern-lru'; // import * as LRU from 'modern-lru';
@@ -82,17 +83,19 @@ export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
Meta, Meta,
globalConfig
// { provide: AUTO_PREBOOT, useValue: false } // turn off auto preboot complete // { provide: AUTO_PREBOOT, useValue: false } // turn off auto preboot complete
] ]
}) })
export class MainModule { export class MainModule {
constructor(public store: Store<AppState>) { constructor(public store: Store<AppState>, @Inject(GLOBAL_CONFIG) private config: GlobalConfig, ) {
// TODO(gdi2290): refactor into a lifecycle hook // TODO(gdi2290): refactor into a lifecycle hook
this.doRehydrate(); this.doRehydrate();
} }
doRehydrate() { doRehydrate() {
if (GlobalConfig.universal.shouldRehydrate) { if (this.config.universal.shouldRehydrate) {
let defaultValue = {}; let defaultValue = {};
let serverCache = this._getCacheValue(NGRX_CACHE_KEY, defaultValue); let serverCache = this._getCacheValue(NGRX_CACHE_KEY, defaultValue);
this.store.dispatch(new RehydrateStoreAction(serverCache)); this.store.dispatch(new RehydrateStoreAction(serverCache));

View File

@@ -21,6 +21,8 @@ import { effects } from '../../app/app.effects';
// see https://github.com/angular/angular/pull/12322 // see https://github.com/angular/angular/pull/12322
import { Meta } from '../angular2-meta'; import { Meta } from '../angular2-meta';
import { globalConfig } from '../../config';
export function createTranslateLoader(http: Http) { export function createTranslateLoader(http: Http) {
return new TranslateStaticLoader(http, './assets/i18n', '.json'); return new TranslateStaticLoader(http, './assets/i18n', '.json');
} }
@@ -70,6 +72,8 @@ export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
{ provide: 'LRU', useFactory: getLRU, deps: [] }, { provide: 'LRU', useFactory: getLRU, deps: [] },
Meta, Meta,
globalConfig
] ]
}) })
export class MainModule { export class MainModule {