no more build errors, still issues with the selectors not returning anything

This commit is contained in:
Art Lowel
2017-07-27 15:12:50 +02:00
parent 49467397ff
commit 05626e2607
29 changed files with 225 additions and 227 deletions

View File

@@ -80,9 +80,9 @@
"@angularclass/bootloader": "1.0.1", "@angularclass/bootloader": "1.0.1",
"@angularclass/idle-preload": "1.0.4", "@angularclass/idle-preload": "1.0.4",
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.28", "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.28",
"@ngrx/effects": "2.0.4", "@ngrx/effects": "^4.0.1",
"@ngrx/router-store": "1.2.6", "@ngrx/router-store": "^4.0.0",
"@ngrx/store": "2.2.3", "@ngrx/store": "^4.0.0",
"@nguniversal/express-engine": "1.0.0-beta.2", "@nguniversal/express-engine": "1.0.0-beta.2",
"@ngx-translate/core": "7.1.0", "@ngx-translate/core": "7.1.0",
"@ngx-translate/http-loader": "0.1.0", "@ngx-translate/http-loader": "0.1.0",
@@ -112,7 +112,7 @@
"devDependencies": { "devDependencies": {
"@angular/compiler": "4.3.1", "@angular/compiler": "4.3.1",
"@angular/compiler-cli": "4.3.1", "@angular/compiler-cli": "4.3.1",
"@ngrx/store-devtools": "3.2.4", "@ngrx/store-devtools": "^4.0.0",
"@ngtools/webpack": "1.5.1", "@ngtools/webpack": "1.5.1",
"@types/cookie-parser": "1.3.30", "@types/cookie-parser": "1.3.30",
"@types/deep-freeze": "0.1.1", "@types/deep-freeze": "0.1.1",
@@ -142,6 +142,7 @@
"imports-loader": "0.7.1", "imports-loader": "0.7.1",
"istanbul-instrumenter-loader": "2.0.0", "istanbul-instrumenter-loader": "2.0.0",
"jasmine-core": "2.6.4", "jasmine-core": "2.6.4",
"jasmine-marbles": "^0.1.0",
"jasmine-spec-reporter": "4.1.1", "jasmine-spec-reporter": "4.1.1",
"json-loader": "0.5.4", "json-loader": "0.5.4",
"karma": "1.7.0", "karma": "1.7.0",
@@ -157,7 +158,6 @@
"karma-sourcemap-loader": "0.3.7", "karma-sourcemap-loader": "0.3.7",
"karma-webdriver-launcher": "1.0.5", "karma-webdriver-launcher": "1.0.5",
"karma-webpack": "2.0.4", "karma-webpack": "2.0.4",
"ngrx-store-freeze": "0.1.9",
"node-sass": "4.5.3", "node-sass": "4.5.3",
"nodemon": "1.11.0", "nodemon": "1.11.0",
"npm-run-all": "4.0.2", "npm-run-all": "4.0.2",

View File

@@ -1,11 +1,8 @@
import { EffectsModule } from '@ngrx/effects';
import { HeaderEffects } from './header/header.effects'; import { HeaderEffects } from './header/header.effects';
import { StoreEffects } from './store.effects'; import { StoreEffects } from './store.effects';
import { coreEffects } from './core/core.effects';
export const effects = [ export const appEffects = [
...coreEffects, // TODO: should probably be imported in coreModule StoreEffects,
EffectsModule.run(StoreEffects), HeaderEffects
EffectsModule.run(HeaderEffects)
]; ];

View File

@@ -0,0 +1,37 @@
import { isNotEmpty } from './shared/empty.util';
import { StoreActionTypes } from './store.actions';
// crude temporary ngrx debugger for use until
// https://github.com/ngrx/platform/issues/97 is fixed
let actionCounter = 0;
export function debugMetaReducer(reducer) {
return (state, action) => {
if (isNotEmpty(console.debug)) {
actionCounter++;
console.debug('@ngrx action', actionCounter, action.type);
console.debug('state', state);
console.debug('action', action);
console.debug('------------------------------------');
}
return reducer(state, action);
}
}
export function universalMetaReducer(reducer) {
return (state, action) => {
switch (action.type) {
case StoreActionTypes.REHYDRATE:
state = Object.assign({}, state, action.payload);
break;
case StoreActionTypes.REPLAY:
break;
default:
return reducer(state, action);
}
}
}
export const appMetaReducers = [debugMetaReducer, universalMetaReducer];

View File

@@ -2,12 +2,11 @@ import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { StoreModule, Store } from '@ngrx/store'; import { StoreModule } from '@ngrx/store';
import { RouterStoreModule } from '@ngrx/router-store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { rootReducer, AppState } from './app.reducer'; import { appReducers } from './app.reducer';
import { effects } from './app.effects'; import { appEffects } from './app.effects';
import { CoreModule } from './core/core.module'; import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module'; import { SharedModule } from './shared/shared.module';
@@ -26,6 +25,8 @@ import { HeaderComponent } from './header/header.component';
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
import { GLOBAL_CONFIG, ENV_CONFIG } from '../config'; import { GLOBAL_CONFIG, ENV_CONFIG } from '../config';
import { EffectsModule } from '@ngrx/effects';
import { appMetaReducers } from './app.metareducers';
export function getConfig() { export function getConfig() {
return ENV_CONFIG; return ENV_CONFIG;
@@ -43,10 +44,9 @@ export function getConfig() {
CollectionPageModule, CollectionPageModule,
CommunityPageModule, CommunityPageModule,
AppRoutingModule, AppRoutingModule,
StoreModule.provideStore(rootReducer), StoreModule.forRoot(appReducers, { metaReducers: appMetaReducers }),
RouterStoreModule.connectRouter(), // !getConfig().production ? StoreDevtoolsModule.instrument({ maxAge: 50 }) : [],
StoreDevtoolsModule.instrumentOnlyWithExtension(), EffectsModule.forRoot(appEffects)
effects
], ],
providers: [ providers: [
{ provide: GLOBAL_CONFIG, useFactory: (getConfig) }, { provide: GLOBAL_CONFIG, useFactory: (getConfig) },

View File

@@ -1,45 +1,17 @@
import { combineReducers, ActionReducer } from '@ngrx/store'; import { ActionReducerMap } from '@ngrx/store';
import { routerReducer, RouterState } from '@ngrx/router-store'; import * as fromRouter from '@ngrx/router-store';
import { storeFreeze } from 'ngrx-store-freeze';
import { compose } from '@ngrx/store';
import { headerReducer, HeaderState } from './header/header.reducer'; import { headerReducer, HeaderState } from './header/header.reducer';
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
import { CoreState, coreReducer } from './core/core.reducers';
import { StoreActionTypes } from './store.actions';
import { ENV_CONFIG } from '../config';
export interface AppState { export interface AppState {
core: CoreState; router: fromRouter.RouterReducerState;
router: RouterState;
hostWindow: HostWindowState; hostWindow: HostWindowState;
header: HeaderState; header: HeaderState;
} }
export const reducers = { export const appReducers: ActionReducerMap<AppState> = {
core: coreReducer, router: fromRouter.routerReducer,
router: routerReducer,
hostWindow: hostWindowReducer, hostWindow: hostWindowReducer,
header: headerReducer header: headerReducer
}; };
export function rootReducer(state: any, action: any) {
switch (action.type) {
case StoreActionTypes.REHYDRATE:
state = Object.assign({}, state, action.payload);
break;
case StoreActionTypes.REPLAY:
break;
default:
}
let root: ActionReducer<any>;
// TODO: attempt to not use InjectionToken GLOBAL_CONFIG over GlobalConfig ENV_CONFIG
if (ENV_CONFIG.production) {
root = combineReducers(reducers)(state, action);
} else {
root = compose(storeFreeze, combineReducers)(reducers)(state, action);
}
return root;
}

View File

@@ -25,6 +25,7 @@ import { CoreModule } from './core/core.module';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
export function init(cache: TransferState) { export function init(cache: TransferState) {
return () => { return () => {
@@ -44,6 +45,7 @@ export function HttpLoaderFactory(http: Http) {
}), }),
IdlePreloadModule.forRoot(), // forRoot ensures the providers are only created once IdlePreloadModule.forRoot(), // forRoot ensures the providers are only created once
RouterModule.forRoot([], { useHash: false, preloadingStrategy: IdlePreload }), RouterModule.forRoot([], { useHash: false, preloadingStrategy: IdlePreload }),
StoreRouterConnectingModule,
TranslateModule.forRoot({ TranslateModule.forRoot({
loader: { loader: {
provide: TranslateLoader, provide: TranslateLoader,
@@ -56,7 +58,7 @@ export function HttpLoaderFactory(http: Http) {
BrowserDataLoaderModule, BrowserDataLoaderModule,
BrowserTransferStateModule, BrowserTransferStateModule,
BrowserTransferStoreModule, BrowserTransferStoreModule,
EffectsModule.run(BrowserTransferStoreEffects), EffectsModule.forRoot([BrowserTransferStoreEffects]),
BrowserAnimationsModule, BrowserAnimationsModule,
AppModule AppModule
], ],

View File

@@ -1,12 +1,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { CacheableObject } from '../object-cache.reducer'; import { CacheableObject } from '../object-cache.reducer';
import { ObjectCacheService } from '../object-cache.service'; import { ObjectCacheService } from '../object-cache.service';
import { RequestService } from '../../data/request.service'; import { RequestService } from '../../data/request.service';
import { ResponseCacheService } from '../response-cache.service'; import { ResponseCacheService } from '../response-cache.service';
import { CoreState } from '../../core.reducers';
import { RequestEntry } from '../../data/request.reducer'; import { RequestEntry } from '../../data/request.reducer';
import { hasValue, isNotEmpty } from '../../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../../shared/empty.util';
import { ResponseCacheEntry } from '../response-cache.reducer'; import { ResponseCacheEntry } from '../response-cache.reducer';
@@ -22,8 +20,7 @@ export class RemoteDataBuildService {
constructor( constructor(
protected objectCache: ObjectCacheService, protected objectCache: ObjectCacheService,
protected responseCache: ResponseCacheService, protected responseCache: ResponseCacheService,
protected requestService: RequestService, protected requestService: RequestService
protected store: Store<CoreState>,
) { ) {
} }
@@ -34,9 +31,9 @@ export class RemoteDataBuildService {
const requestHrefObs = this.objectCache.getRequestHrefBySelfLink(href); const requestHrefObs = this.objectCache.getRequestHrefBySelfLink(href);
const requestObs = Observable.race( const requestObs = Observable.race(
this.store.select<RequestEntry>('core', 'data', 'request', href).filter((entry) => hasValue(entry)), this.requestService.get(href).filter((entry) => hasValue(entry)),
requestHrefObs.flatMap((requestHref) => requestHrefObs.flatMap((requestHref) =>
this.store.select<RequestEntry>('core', 'data', 'request', requestHref)).filter((entry) => hasValue(entry)) this.requestService.get(requestHref)).filter((entry) => hasValue(entry))
); );
const responseCacheObs = Observable.race( const responseCacheObs = Observable.race(
@@ -111,7 +108,7 @@ export class RemoteDataBuildService {
href: string, href: string,
normalizedType: GenericConstructor<TNormalized> normalizedType: GenericConstructor<TNormalized>
): RemoteData<TDomain[]> { ): RemoteData<TDomain[]> {
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', href) const requestObs = this.requestService.get(href)
.filter((entry) => hasValue(entry)); .filter((entry) => hasValue(entry));
const responseCacheObs = this.responseCache.get(href).filter((entry) => hasValue(entry)); const responseCacheObs = this.responseCache.get(href).filter((entry) => hasValue(entry));

View File

@@ -1,18 +0,0 @@
import { combineReducers } from '@ngrx/store';
import { ResponseCacheState, responseCacheReducer } from './response-cache.reducer';
import { ObjectCacheState, objectCacheReducer } from './object-cache.reducer';
export interface CacheState {
response: ResponseCacheState,
object: ObjectCacheState
}
export const reducers = {
response: responseCacheReducer,
object: objectCacheReducer
};
export function cacheReducer(state: any, action: any) {
return combineReducers(reducers)(state, action);
}

View File

@@ -48,7 +48,7 @@ const initialState: ObjectCacheState = Object.create(null);
* @return ObjectCacheState * @return ObjectCacheState
* the new state * the new state
*/ */
export const objectCacheReducer = (state = initialState, action: ObjectCacheAction): ObjectCacheState => { export function objectCacheReducer(state = initialState, action: ObjectCacheAction): ObjectCacheState {
switch (action.type) { switch (action.type) {
case ObjectCacheActionTypes.ADD: { case ObjectCacheActionTypes.ADD: {
@@ -67,7 +67,7 @@ export const objectCacheReducer = (state = initialState, action: ObjectCacheActi
return state; return state;
} }
} }
}; }
/** /**
* Add an object to the cache * Add an object to the cache

View File

@@ -1,12 +1,22 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { MemoizedSelector, Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { ObjectCacheState, ObjectCacheEntry, CacheableObject } from './object-cache.reducer'; import { ObjectCacheEntry, CacheableObject } from './object-cache.reducer';
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions'; import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions';
import { hasNoValue } from '../../shared/empty.util'; import { hasNoValue } from '../../shared/empty.util';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
import { CoreState } from '../core.reducers';
import { keySelector } from '../shared/selectors';
function objectFromUuidSelector(uuid: string): MemoizedSelector<CoreState, ObjectCacheEntry> {
return keySelector<ObjectCacheEntry>('data/object', uuid);
}
function uuidFromHrefSelector(href: string): MemoizedSelector<CoreState, string> {
return keySelector<string>('index/href', href);
}
/** /**
* A service to interact with the object cache * A service to interact with the object cache
@@ -14,7 +24,7 @@ import { GenericConstructor } from '../shared/generic-constructor';
@Injectable() @Injectable()
export class ObjectCacheService { export class ObjectCacheService {
constructor( constructor(
private store: Store<ObjectCacheState> private store: Store<CoreState>
) { } ) { }
/** /**
@@ -65,12 +75,12 @@ export class ObjectCacheService {
} }
getBySelfLink<T extends CacheableObject>(href: string, type: GenericConstructor<T>): Observable<T> { getBySelfLink<T extends CacheableObject>(href: string, type: GenericConstructor<T>): Observable<T> {
return this.store.select<string>('core', 'index', 'href', href) return this.store.select(uuidFromHrefSelector(href))
.flatMap((uuid: string) => this.get(uuid, type)) .flatMap((uuid: string) => this.get(uuid, type))
} }
private getEntry(uuid: string): Observable<ObjectCacheEntry> { private getEntry(uuid: string): Observable<ObjectCacheEntry> {
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid) return this.store.select(objectFromUuidSelector(uuid))
.filter((entry) => this.isValid(entry)) .filter((entry) => this.isValid(entry))
.distinctUntilChanged(); .distinctUntilChanged();
} }
@@ -82,7 +92,7 @@ export class ObjectCacheService {
} }
getRequestHrefBySelfLink(self: string): Observable<string> { getRequestHrefBySelfLink(self: string): Observable<string> {
return this.store.select<string>('core', 'index', 'href', self) return this.store.select('index/href', self)
.flatMap((uuid: string) => this.getRequestHref(uuid)); .flatMap((uuid: string) => this.getRequestHref(uuid));
} }
@@ -123,8 +133,9 @@ export class ObjectCacheService {
has(uuid: string): boolean { has(uuid: string): boolean {
let result: boolean; let result: boolean;
this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid) this.store.select(objectFromUuidSelector(uuid))
.take(1) .take(1)
.do((entry: ObjectCacheEntry) => console.log(entry))
.subscribe((entry) => result = this.isValid(entry)); .subscribe((entry) => result = this.isValid(entry));
return result; return result;
@@ -142,7 +153,7 @@ export class ObjectCacheService {
hasBySelfLink(href: string): boolean { hasBySelfLink(href: string): boolean {
let result = false; let result = false;
this.store.select<string>('core', 'index', 'href', href) this.store.select(uuidFromHrefSelector(href))
.take(1) .take(1)
.subscribe((uuid: string) => result = this.has(uuid)); .subscribe((uuid: string) => result = this.has(uuid));

View File

@@ -37,7 +37,7 @@ const initialState = Object.create(null);
* @return ResponseCacheState * @return ResponseCacheState
* the new state * the new state
*/ */
export const responseCacheReducer = (state = initialState, action: ResponseCacheAction): ResponseCacheState => { export function responseCacheReducer(state = initialState, action: ResponseCacheAction): ResponseCacheState {
switch (action.type) { switch (action.type) {
case ResponseCacheActionTypes.ADD: { case ResponseCacheActionTypes.ADD: {
@@ -56,7 +56,7 @@ export const responseCacheReducer = (state = initialState, action: ResponseCache
return state; return state;
} }
} }
}; }
function addToCache(state: ResponseCacheState, action: ResponseCacheAddAction): ResponseCacheState { function addToCache(state: ResponseCacheState, action: ResponseCacheAddAction): ResponseCacheState {
return Object.assign({}, state, { return Object.assign({}, state, {

View File

@@ -1,12 +1,18 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { MemoizedSelector, Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { ResponseCacheState, ResponseCacheEntry } from './response-cache.reducer'; import { ResponseCacheEntry } from './response-cache.reducer';
import { hasNoValue } from '../../shared/empty.util'; import { hasNoValue } from '../../shared/empty.util';
import { ResponseCacheRemoveAction, ResponseCacheAddAction } from './response-cache.actions'; import { ResponseCacheRemoveAction, ResponseCacheAddAction } from './response-cache.actions';
import { Response } from './response-cache.models'; import { Response } from './response-cache.models';
import { CoreState } from '../core.reducers';
import { keySelector } from '../shared/selectors';
function entryFromKeySelector(key: string): MemoizedSelector<CoreState, ResponseCacheEntry> {
return keySelector<ResponseCacheEntry>('data/reponse', key);
}
/** /**
* A service to interact with the response cache * A service to interact with the response cache
@@ -14,7 +20,7 @@ import { Response } from './response-cache.models';
@Injectable() @Injectable()
export class ResponseCacheService { export class ResponseCacheService {
constructor( constructor(
private store: Store<ResponseCacheState> private store: Store<CoreState>
) { } ) { }
add(key: string, response: Response, msToLive: number): Observable<ResponseCacheEntry> { add(key: string, response: Response, msToLive: number): Observable<ResponseCacheEntry> {
@@ -34,7 +40,7 @@ export class ResponseCacheService {
* an observable of the ResponseCacheEntry with the specified key * an observable of the ResponseCacheEntry with the specified key
*/ */
get(key: string): Observable<ResponseCacheEntry> { get(key: string): Observable<ResponseCacheEntry> {
return this.store.select<ResponseCacheEntry>('core', 'cache', 'response', key) return this.store.select(entryFromKeySelector(key))
.filter((entry) => this.isValid(entry)) .filter((entry) => this.isValid(entry))
} }
@@ -50,8 +56,9 @@ export class ResponseCacheService {
has(key: string): boolean { has(key: string): boolean {
let result: boolean; let result: boolean;
this.store.select<ResponseCacheEntry>('core', 'cache', 'response', key) this.store.select(entryFromKeySelector(key))
.take(1) .take(1)
.do((entry) => console.log('ResponseCacheEntry', entry))
.subscribe((entry) => { .subscribe((entry) => {
result = this.isValid(entry); result = this.isValid(entry);
}); });

View File

@@ -1,4 +1,3 @@
import { EffectsModule } from '@ngrx/effects';
import { ObjectCacheEffects } from './data/object-cache.effects'; import { ObjectCacheEffects } from './data/object-cache.effects';
import { RequestCacheEffects } from './data/request-cache.effects'; import { RequestCacheEffects } from './data/request-cache.effects';
@@ -6,7 +5,8 @@ import { HrefIndexEffects } from './index/href-index.effects';
import { RequestEffects } from './data/request.effects'; import { RequestEffects } from './data/request.effects';
export const coreEffects = [ export const coreEffects = [
EffectsModule.run(RequestEffects), RequestCacheEffects,
EffectsModule.run(ObjectCacheEffects), RequestEffects,
EffectsModule.run(HrefIndexEffects), ObjectCacheEffects,
HrefIndexEffects,
]; ];

View File

@@ -14,10 +14,16 @@ import { RequestService } from './data/request.service';
import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from './cache/builders/remote-data-build.service';
import { CommunityDataService } from './data/community-data.service'; import { CommunityDataService } from './data/community-data.service';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { coreEffects } from './core.effects';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { coreReducers } from './core.reducers';
const IMPORTS = [ const IMPORTS = [
CommonModule, CommonModule,
SharedModule SharedModule,
StoreModule.forFeature('core', coreReducers, { }),
EffectsModule.forFeature(coreEffects)
]; ];
const DECLARATIONS = [ const DECLARATIONS = [

View File

@@ -1,21 +1,22 @@
import { combineReducers } from '@ngrx/store'; import { ActionReducerMap, createFeatureSelector } from '@ngrx/store';
import { CacheState, cacheReducer } from './cache/cache.reducers'; import { responseCacheReducer, ResponseCacheState } from './cache/response-cache.reducer';
import { IndexState, indexReducer } from './index/index.reducers'; import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reducer';
import { DataState, dataReducer } from './data/data.reducers'; import { hrefIndexReducer, HrefIndexState } from './index/href-index.reducer';
import { requestReducer, RequestState } from './data/request.reducer';
export interface CoreState { export interface CoreState {
cache: CacheState, 'data/object': ObjectCacheState,
index: IndexState, 'data/response': ResponseCacheState,
data: DataState 'data/request': RequestState,
'index/href': HrefIndexState
} }
export const reducers = { export const coreReducers: ActionReducerMap<CoreState> = {
cache: cacheReducer, 'data/object': objectCacheReducer,
index: indexReducer, 'data/response': responseCacheReducer,
data: dataReducer 'data/request': requestReducer,
'index/href': hrefIndexReducer
}; };
export function coreReducer(state: any, action: any) { export const coreSelector = createFeatureSelector<CoreState>('core');
return combineReducers(reducers)(state, action);
}

View File

@@ -1,15 +0,0 @@
import { combineReducers } from '@ngrx/store';
import { RequestState, requestReducer } from './request.reducer';
export interface DataState {
request: RequestState
}
export const reducers = {
request: requestReducer
};
export function dataReducer(state: any, action: any) {
return combineReducers(reducers)(state, action);
}

View File

@@ -19,7 +19,7 @@ export interface RequestState {
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`) // Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
const initialState = Object.create(null); const initialState = Object.create(null);
export const requestReducer = (state = initialState, action: RequestAction): RequestState => { export function requestReducer(state = initialState, action: RequestAction): RequestState {
switch (action.type) { switch (action.type) {
case RequestActionTypes.CONFIGURE: { case RequestActionTypes.CONFIGURE: {
@@ -38,7 +38,7 @@ export const requestReducer = (state = initialState, action: RequestAction): Req
return state; return state;
} }
} }
}; }
function configureRequest(state: RequestState, action: RequestConfigureAction): RequestState { function configureRequest(state: RequestState, action: RequestConfigureAction): RequestState {
return Object.assign({}, state, { return Object.assign({}, state, {

View File

@@ -1,8 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { MemoizedSelector, Store } from '@ngrx/store';
import { request } from 'http';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
@@ -15,6 +13,12 @@ import { ObjectCacheService } from '../cache/object-cache.service';
import { CacheableObject } from '../cache/object-cache.reducer'; import { CacheableObject } from '../cache/object-cache.reducer';
import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { SuccessResponse } from '../cache/response-cache.models'; import { SuccessResponse } from '../cache/response-cache.models';
import { CoreState } from '../core.reducers';
import { keySelector } from '../shared/selectors';
function entryFromHrefSelector(href: string): MemoizedSelector<CoreState, RequestEntry> {
return keySelector<RequestEntry>('data/request', href);
}
@Injectable() @Injectable()
export class RequestService { export class RequestService {
@@ -22,13 +26,13 @@ export class RequestService {
constructor( constructor(
private objectCache: ObjectCacheService, private objectCache: ObjectCacheService,
private responseCache: ResponseCacheService, private responseCache: ResponseCacheService,
private store: Store<RequestState> private store: Store<CoreState>
) { ) {
} }
isPending(href: string): boolean { isPending(href: string): boolean {
let isPending = false; let isPending = false;
this.store.select<RequestEntry>('core', 'data', 'request', href) this.store.select(entryFromHrefSelector(href))
.take(1) .take(1)
.subscribe((re: RequestEntry) => { .subscribe((re: RequestEntry) => {
isPending = (hasValue(re) && !re.completed) isPending = (hasValue(re) && !re.completed)
@@ -38,7 +42,7 @@ export class RequestService {
} }
get(href: string): Observable<RequestEntry> { get(href: string): Observable<RequestEntry> {
return this.store.select<RequestEntry>('core', 'data', 'request', href); return this.store.select(entryFromHrefSelector(href));
} }
configure<T extends CacheableObject>(request: Request<T>): void { configure<T extends CacheableObject>(request: Request<T>): void {

View File

@@ -12,7 +12,7 @@ export interface HrefIndexState {
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`) // Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
const initialState: HrefIndexState = Object.create(null); const initialState: HrefIndexState = Object.create(null);
export const hrefIndexReducer = (state = initialState, action: HrefIndexAction): HrefIndexState => { export function hrefIndexReducer(state = initialState, action: HrefIndexAction): HrefIndexState {
switch (action.type) { switch (action.type) {
case HrefIndexActionTypes.ADD: { case HrefIndexActionTypes.ADD: {
@@ -27,7 +27,7 @@ export const hrefIndexReducer = (state = initialState, action: HrefIndexAction):
return state; return state;
} }
} }
}; }
function addToHrefIndex(state: HrefIndexState, action: AddToHrefIndexAction): HrefIndexState { function addToHrefIndex(state: HrefIndexState, action: AddToHrefIndexAction): HrefIndexState {
return Object.assign({}, state, { return Object.assign({}, state, {

View File

@@ -1,15 +0,0 @@
import { combineReducers } from '@ngrx/store';
import { HrefIndexState, hrefIndexReducer } from './href-index.reducer';
export interface IndexState {
href: HrefIndexState
}
export const reducers = {
href: hrefIndexReducer
};
export function indexReducer(state: any, action: any) {
return combineReducers(reducers)(state, action);
}

View File

@@ -0,0 +1,13 @@
import { createSelector, MemoizedSelector } from '@ngrx/store';
import { coreSelector, CoreState } from '../core.reducers';
import { hasValue } from '../../shared/empty.util';
export function keySelector<T>(subState: string, key: string): MemoizedSelector<CoreState, T> {
return createSelector(coreSelector, (state: CoreState) => {
if (hasValue(state[subState])) {
return state[subState][key];
} else {
return undefined;
}
});
}

View File

@@ -1,9 +1,13 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store'; import { createSelector, Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { HeaderState } from './header.reducer'; import { HeaderState } from './header.reducer';
import { HeaderToggleAction } from './header.actions'; import { HeaderToggleAction } from './header.actions';
import { AppState } from '../app.reducer';
const headerStateSelector = (state: AppState) => state.header;
const navCollapsedSelector = createSelector(headerStateSelector, (header: HeaderState) => header.navCollapsed);
@Component({ @Component({
selector: 'ds-header', selector: 'ds-header',
@@ -14,14 +18,12 @@ export class HeaderComponent implements OnInit {
public isNavBarCollapsed: Observable<boolean>; public isNavBarCollapsed: Observable<boolean>;
constructor( constructor(
private store: Store<HeaderState> private store: Store<AppState>
) { ) {
} }
ngOnInit(): void { ngOnInit(): void {
this.isNavBarCollapsed = this.store.select('header') this.isNavBarCollapsed = this.store.select(navCollapsedSelector);
// unwrap navCollapsed
.map(({ navCollapsed }: HeaderState) => navCollapsed);
} }
public toggle(): void { public toggle(): void {

View File

@@ -1,41 +1,36 @@
import { TestBed, inject } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { EffectsTestingModule, EffectsRunner } from '@ngrx/effects/testing';
import { routerActions } from '@ngrx/router-store';
import { HeaderEffects } from './header.effects'; import { HeaderEffects } from './header.effects';
import { HeaderCollapseAction } from './header.actions'; import { HeaderCollapseAction } from './header.actions';
import { HostWindowResizeAction } from '../shared/host-window.actions'; import { HostWindowResizeAction } from '../shared/host-window.actions';
import { Observable } from 'rxjs/Observable';
import { provideMockActions } from '@ngrx/effects/testing';
import { cold, hot } from 'jasmine-marbles';
import * as fromRouter from '@ngrx/router-store';
describe('HeaderEffects', () => { describe('HeaderEffects', () => {
beforeEach(() => TestBed.configureTestingModule({
imports: [
EffectsTestingModule
],
providers: [
HeaderEffects
]
}));
let runner: EffectsRunner;
let headerEffects: HeaderEffects; let headerEffects: HeaderEffects;
let actions: Observable<any>;
beforeEach(inject([ beforeEach(() => {
EffectsRunner, HeaderEffects TestBed.configureTestingModule({
providers: [
HeaderEffects,
provideMockActions(() => actions),
// other providers
], ],
(_runner, _headerEffects) => { });
runner = _runner;
headerEffects = _headerEffects; headerEffects = TestBed.get(HeaderEffects);
} });
));
describe('resize$', () => { describe('resize$', () => {
it('should return a COLLAPSE action in response to a RESIZE action', () => { it('should return a COLLAPSE action in response to a RESIZE action', () => {
runner.queue(new HostWindowResizeAction(800, 600)); actions = hot('--a-', { a: new HostWindowResizeAction(800, 600) });
headerEffects.resize$.subscribe((result) => { const expected = cold('--b-', { b: new HeaderCollapseAction() });
expect(result).toEqual(new HeaderCollapseAction());
}); expect(headerEffects.resize$).toBeObservable(expected);
}); });
}); });
@@ -43,11 +38,11 @@ describe('HeaderEffects', () => {
describe('routeChange$', () => { describe('routeChange$', () => {
it('should return a COLLAPSE action in response to an UPDATE_LOCATION action', () => { it('should return a COLLAPSE action in response to an UPDATE_LOCATION action', () => {
runner.queue({ type: routerActions.UPDATE_LOCATION }); actions = hot('--a-', { a: { type: fromRouter.ROUTER_NAVIGATION } });
headerEffects.resize$.subscribe((result) => { const expected = cold('--b-', { b: new HeaderCollapseAction() });
expect(result).toEqual(new HeaderCollapseAction());
}); expect(headerEffects.routeChange$).toBeObservable(expected);
}); });
}); });

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Effect, Actions } from '@ngrx/effects' import { Effect, Actions } from '@ngrx/effects'
import { routerActions } from '@ngrx/router-store'; import * as fromRouter from '@ngrx/router-store';
import { HostWindowActionTypes } from '../shared/host-window.actions'; import { HostWindowActionTypes } from '../shared/host-window.actions';
import { HeaderCollapseAction } from './header.actions'; import { HeaderCollapseAction } from './header.actions';
@@ -13,7 +13,7 @@ export class HeaderEffects {
.map(() => new HeaderCollapseAction()); .map(() => new HeaderCollapseAction());
@Effect() routeChange$ = this.actions$ @Effect() routeChange$ = this.actions$
.ofType(routerActions.UPDATE_LOCATION) .ofType(fromRouter.ROUTER_NAVIGATION)
.map(() => new HeaderCollapseAction()); .map(() => new HeaderCollapseAction());
constructor(private actions$: Actions) { constructor(private actions$: Actions) {

View File

@@ -8,7 +8,7 @@ const initialState: HeaderState = {
navCollapsed: true navCollapsed: true
}; };
export const headerReducer = (state = initialState, action: HeaderAction): HeaderState => { export function headerReducer(state = initialState, action: HeaderAction): HeaderState {
switch (action.type) { switch (action.type) {
case HeaderActionTypes.COLLAPSE: { case HeaderActionTypes.COLLAPSE: {
@@ -35,4 +35,4 @@ export const headerReducer = (state = initialState, action: HeaderAction): Heade
return state; return state;
} }
} }
}; }

View File

@@ -1,7 +1,7 @@
import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/first'; import 'rxjs/add/operator/first';
import { ApplicationRef, Inject, NgModule, APP_BOOTSTRAP_LISTENER } from '@angular/core'; import { ApplicationRef, NgModule, APP_BOOTSTRAP_LISTENER } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { ServerModule } from '@angular/platform-server'; import { ServerModule } from '@angular/platform-server';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
@@ -16,14 +16,13 @@ import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Actions, EffectsModule } from '@ngrx/effects'; import { EffectsModule } from '@ngrx/effects';
import { TranslateUniversalLoader } from '../modules/translate-universal-loader'; import { TranslateUniversalLoader } from '../modules/translate-universal-loader';
import { ServerTransferStateModule } from '../modules/transfer-state/server-transfer-state.module'; import { ServerTransferStateModule } from '../modules/transfer-state/server-transfer-state.module';
import { TransferState } from '../modules/transfer-state/transfer-state'; import { TransferState } from '../modules/transfer-state/transfer-state';
import { TransferStoreEffects } from '../modules/transfer-store/transfer-store.effects';
import { ServerTransferStoreEffects } from '../modules/transfer-store/server-transfer-store.effects'; import { ServerTransferStoreEffects } from '../modules/transfer-store/server-transfer-store.effects';
import { ServerTransferStoreModule } from '../modules/transfer-store/server-transfer-store.module'; import { ServerTransferStoreModule } from '../modules/transfer-store/server-transfer-store.module';
@@ -32,15 +31,13 @@ import { ServerCookiesModule } from '../modules/cookies/server-cookies.module';
import { ServerDataLoaderModule } from '../modules/data-loader/server-data-loader.module'; import { ServerDataLoaderModule } from '../modules/data-loader/server-data-loader.module';
import { AppState } from './app.reducer'; import { AppState } from './app.reducer';
import { effects } from './app.effects';
import { SharedModule } from './shared/shared.module';
import { CoreModule } from './core/core.module';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { GLOBAL_CONFIG, GlobalConfig } from '../config'; import { GLOBAL_CONFIG, GlobalConfig } from '../config';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
export function boot(cache: TransferState, appRef: ApplicationRef, store: Store<AppState>, request: Request, config: GlobalConfig) { export function boot(cache: TransferState, appRef: ApplicationRef, store: Store<AppState>, request: Request, config: GlobalConfig) {
// authentication mechanism goes here // authentication mechanism goes here
@@ -61,6 +58,7 @@ export function UniversalLoaderFactory() {
appId: 'ds-app-id' appId: 'ds-app-id'
}), }),
RouterModule.forRoot([], { useHash: false }), RouterModule.forRoot([], { useHash: false }),
StoreRouterConnectingModule,
TranslateModule.forRoot({ TranslateModule.forRoot({
loader: { loader: {
provide: TranslateLoader, provide: TranslateLoader,
@@ -74,7 +72,7 @@ export function UniversalLoaderFactory() {
ServerDataLoaderModule, ServerDataLoaderModule,
ServerTransferStateModule, ServerTransferStateModule,
ServerTransferStoreModule, ServerTransferStoreModule,
EffectsModule.run(ServerTransferStoreEffects), EffectsModule.forRoot([ServerTransferStoreEffects]),
NoopAnimationsModule, NoopAnimationsModule,
AppModule AppModule
], ],

View File

@@ -10,7 +10,7 @@ const initialState: HostWindowState = {
height: null height: null
}; };
export const hostWindowReducer = (state = initialState, action: HostWindowAction): HostWindowState => { export function hostWindowReducer(state = initialState, action: HostWindowAction): HostWindowState {
switch (action.type) { switch (action.type) {
case HostWindowActionTypes.RESIZE: { case HostWindowActionTypes.RESIZE: {
@@ -21,4 +21,4 @@ export const hostWindowReducer = (state = initialState, action: HostWindowAction
return state; return state;
} }
} }
}; }

View File

@@ -1,9 +1,10 @@
import { HostWindowState } from './host-window.reducer'; import { HostWindowState } from './host-window.reducer';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { createSelector, Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { hasValue } from './empty.util'; import { hasValue } from './empty.util';
import { AppState } from '../app.reducer';
// TODO: ideally we should get these from sass somehow // TODO: ideally we should get these from sass somehow
export enum GridBreakpoint { export enum GridBreakpoint {
@@ -14,16 +15,19 @@ export enum GridBreakpoint {
XL = 1200 XL = 1200
} }
const hostWindowStateSelector = (state: AppState) => state.hostWindow;
const widthSelector = createSelector(hostWindowStateSelector, (hostWindow: HostWindowState) => hostWindow.width);
@Injectable() @Injectable()
export class HostWindowService { export class HostWindowService {
constructor( constructor(
private store: Store<HostWindowState> private store: Store<AppState>
) { ) {
} }
private getWidthObs(): Observable<number> { private getWidthObs(): Observable<number> {
return this.store.select<number>('hostWindow', 'width') return this.store.select(widthSelector)
.filter((width) => hasValue(width)); .filter((width) => hasValue(width));
} }

View File

@@ -90,21 +90,21 @@
version "1.0.0-alpha.28" version "1.0.0-alpha.28"
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-1.0.0-alpha.28.tgz#30a6503bf7f94f9d3187591fb3267b59cc0cdaad" resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-1.0.0-alpha.28.tgz#30a6503bf7f94f9d3187591fb3267b59cc0cdaad"
"@ngrx/effects@2.0.4": "@ngrx/effects@^4.0.1":
version "2.0.4" version "4.0.1"
resolved "https://registry.yarnpkg.com/@ngrx/effects/-/effects-2.0.4.tgz#418eee5e1032fa66de5bbf1855653bb1951f12a4" resolved "https://registry.yarnpkg.com/@ngrx/effects/-/effects-4.0.1.tgz#9403d5668c70217eb5c84ba7a5ffebbbcf507b34"
"@ngrx/router-store@1.2.6": "@ngrx/router-store@^4.0.0":
version "1.2.6" version "4.0.0"
resolved "https://registry.yarnpkg.com/@ngrx/router-store/-/router-store-1.2.6.tgz#a2eb0ca515e9b367781f1030250dd64bb73c086b" resolved "https://registry.yarnpkg.com/@ngrx/router-store/-/router-store-4.0.0.tgz#22b50297395669834df09c46b5a15a8eb56b871d"
"@ngrx/store-devtools@3.2.4": "@ngrx/store-devtools@^4.0.0":
version "3.2.4" version "4.0.0"
resolved "https://registry.yarnpkg.com/@ngrx/store-devtools/-/store-devtools-3.2.4.tgz#2ce4d13bf34848a9e51ec87e3b125ed67b51e550" resolved "https://registry.yarnpkg.com/@ngrx/store-devtools/-/store-devtools-4.0.0.tgz#b79c24773217df7fd9735ad21f9cbf2533c96e04"
"@ngrx/store@2.2.3": "@ngrx/store@^4.0.0":
version "2.2.3" version "4.0.0"
resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-2.2.3.tgz#e7bd1149f1c44208f1cc4744353f0f98a0f1f57b" resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-4.0.0.tgz#714d82056f0eb31518ca85a68a5cfecfc12cc50b"
"@ngtools/webpack@1.5.1": "@ngtools/webpack@1.5.1":
version "1.5.1" version "1.5.1"
@@ -1730,10 +1730,6 @@ deep-extend@~0.4.0:
version "0.4.2" version "0.4.2"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
deep-freeze-strict@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/deep-freeze-strict/-/deep-freeze-strict-1.1.1.tgz#77d0583ca24a69be4bbd9ac2fae415d55523e5b0"
deep-freeze@0.0.1: deep-freeze@0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84"
@@ -3486,6 +3482,12 @@ jasmine-core@2.6.4, jasmine-core@~2.6.0:
version "2.6.4" version "2.6.4"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.6.4.tgz#dec926cd0a9fa287fb6db5c755fa487e74cecac5" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.6.4.tgz#dec926cd0a9fa287fb6db5c755fa487e74cecac5"
jasmine-marbles@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/jasmine-marbles/-/jasmine-marbles-0.1.0.tgz#c9ecdc64e20b6cf55b49a10201a5be33907dadcc"
dependencies:
lodash.isequal "^4.5.0"
jasmine-spec-reporter@4.1.1: jasmine-spec-reporter@4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/jasmine-spec-reporter/-/jasmine-spec-reporter-4.1.1.tgz#5a6d58ab5d61bea7309fbc279239511756b1b588" resolved "https://registry.yarnpkg.com/jasmine-spec-reporter/-/jasmine-spec-reporter-4.1.1.tgz#5a6d58ab5d61bea7309fbc279239511756b1b588"
@@ -3939,6 +3941,10 @@ lodash.isarray@^3.0.0:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
lodash.keys@^3.0.0: lodash.keys@^3.0.0:
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
@@ -4362,12 +4368,6 @@ nested-error-stacks@^1.0.0:
dependencies: dependencies:
inherits "~2.0.1" inherits "~2.0.1"
ngrx-store-freeze@0.1.9:
version "0.1.9"
resolved "https://registry.yarnpkg.com/ngrx-store-freeze/-/ngrx-store-freeze-0.1.9.tgz#b20f18f21fd5efc4e1b1e05f6f279674d0f70c81"
dependencies:
deep-freeze-strict "^1.1.1"
ngx-pagination@3.0.1: ngx-pagination@3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/ngx-pagination/-/ngx-pagination-3.0.1.tgz#5a8000e40c0424d9c41c9d6d592562e1547abf24" resolved "https://registry.yarnpkg.com/ngx-pagination/-/ngx-pagination-3.0.1.tgz#5a8000e40c0424d9c41c9d6d592562e1547abf24"