mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge remote-tracking branch 'origin/master' into w2p-44024_simple-search-with-select
Conflicts: package.json src/app/core/cache/builders/remote-data-build.service.ts src/app/shared/shared.module.ts
This commit is contained in:
@@ -15,8 +15,6 @@ node_js:
|
||||
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
bundler_args: --retry 5
|
||||
|
||||
|
11
package.json
11
package.json
@@ -80,10 +80,9 @@
|
||||
"@angularclass/bootloader": "1.0.1",
|
||||
"@angularclass/idle-preload": "1.0.4",
|
||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.1",
|
||||
"@ngrx/core": "1.2.0",
|
||||
"@ngrx/effects": "2.0.4",
|
||||
"@ngrx/router-store": "1.2.6",
|
||||
"@ngrx/store": "2.2.3",
|
||||
"@ngrx/effects": "^4.0.5",
|
||||
"@ngrx/router-store": "^4.0.4",
|
||||
"@ngrx/store": "^4.0.3",
|
||||
"@nguniversal/express-engine": "1.0.0-beta.2",
|
||||
"@ngx-translate/core": "7.1.0",
|
||||
"@ngx-translate/http-loader": "0.1.0",
|
||||
@@ -113,7 +112,7 @@
|
||||
"devDependencies": {
|
||||
"@angular/compiler": "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",
|
||||
"@types/cookie-parser": "1.3.30",
|
||||
"@types/deep-freeze": "0.1.1",
|
||||
@@ -143,6 +142,7 @@
|
||||
"imports-loader": "0.7.1",
|
||||
"istanbul-instrumenter-loader": "2.0.0",
|
||||
"jasmine-core": "2.6.4",
|
||||
"jasmine-marbles": "^0.1.0",
|
||||
"jasmine-spec-reporter": "4.1.1",
|
||||
"json-loader": "0.5.4",
|
||||
"karma": "1.7.0",
|
||||
@@ -158,7 +158,6 @@
|
||||
"karma-sourcemap-loader": "0.3.7",
|
||||
"karma-webdriver-launcher": "1.0.5",
|
||||
"karma-webpack": "2.0.4",
|
||||
"ngrx-store-freeze": "0.1.9",
|
||||
"node-sass": "4.5.3",
|
||||
"nodemon": "1.11.0",
|
||||
"npm-run-all": "4.0.2",
|
||||
|
@@ -45,7 +45,7 @@ describe('App component', () => {
|
||||
return TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
StoreModule.provideStore({}),
|
||||
StoreModule.forRoot({}),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
|
@@ -1,11 +1,8 @@
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { HeaderEffects } from './header/header.effects';
|
||||
import { StoreEffects } from './store.effects';
|
||||
import { coreEffects } from './core/core.effects';
|
||||
|
||||
export const effects = [
|
||||
...coreEffects, // TODO: should probably be imported in coreModule
|
||||
EffectsModule.run(StoreEffects),
|
||||
EffectsModule.run(HeaderEffects)
|
||||
export const appEffects = [
|
||||
StoreEffects,
|
||||
HeaderEffects
|
||||
];
|
||||
|
38
src/app/app.metareducers.ts
Normal file
38
src/app/app.metareducers.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { isNotEmpty } from './shared/empty.util';
|
||||
import { StoreActionTypes } from './store.actions';
|
||||
|
||||
// fallback ngrx debugger
|
||||
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:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return reducer(state, action);
|
||||
}
|
||||
}
|
||||
|
||||
export const appMetaReducers = [
|
||||
// debugMetaReducer,
|
||||
universalMetaReducer,
|
||||
];
|
@@ -2,12 +2,11 @@ import { NgModule } from '@angular/core';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { StoreModule, Store } from '@ngrx/store';
|
||||
import { RouterStoreModule } from '@ngrx/router-store';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
|
||||
import { rootReducer, AppState } from './app.reducer';
|
||||
import { effects } from './app.effects';
|
||||
import { appReducers } from './app.reducer';
|
||||
import { appEffects } from './app.effects';
|
||||
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
@@ -28,6 +27,8 @@ import { HeaderComponent } from './header/header.component';
|
||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||
|
||||
import { GLOBAL_CONFIG, ENV_CONFIG } from '../config';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { appMetaReducers } from './app.metareducers';
|
||||
|
||||
export function getConfig() {
|
||||
return ENV_CONFIG;
|
||||
@@ -46,10 +47,9 @@ export function getConfig() {
|
||||
CommunityPageModule,
|
||||
SearchPageModule,
|
||||
AppRoutingModule,
|
||||
StoreModule.provideStore(rootReducer),
|
||||
RouterStoreModule.connectRouter(),
|
||||
StoreDevtoolsModule.instrumentOnlyWithExtension(),
|
||||
effects
|
||||
StoreModule.forRoot(appReducers, { metaReducers: appMetaReducers }),
|
||||
StoreDevtoolsModule.instrument({ maxAge: 50 }),
|
||||
EffectsModule.forRoot(appEffects)
|
||||
],
|
||||
providers: [
|
||||
{ provide: GLOBAL_CONFIG, useFactory: (getConfig) },
|
||||
|
@@ -1,45 +1,17 @@
|
||||
import { combineReducers, ActionReducer } from '@ngrx/store';
|
||||
import { routerReducer, RouterState } from '@ngrx/router-store';
|
||||
import { storeFreeze } from 'ngrx-store-freeze';
|
||||
import { compose } from '@ngrx/core';
|
||||
import { ActionReducerMap } from '@ngrx/store';
|
||||
import * as fromRouter from '@ngrx/router-store';
|
||||
|
||||
import { headerReducer, HeaderState } from './header/header.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 {
|
||||
core: CoreState;
|
||||
router: RouterState;
|
||||
router: fromRouter.RouterReducerState;
|
||||
hostWindow: HostWindowState;
|
||||
header: HeaderState;
|
||||
}
|
||||
|
||||
export const reducers = {
|
||||
core: coreReducer,
|
||||
router: routerReducer,
|
||||
export const appReducers: ActionReducerMap<AppState> = {
|
||||
router: fromRouter.routerReducer,
|
||||
hostWindow: hostWindowReducer,
|
||||
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;
|
||||
}
|
||||
|
@@ -25,6 +25,8 @@ import { CoreModule } from './core/core.module';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||
import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer';
|
||||
|
||||
export function init(cache: TransferState) {
|
||||
return () => {
|
||||
@@ -56,7 +58,8 @@ export function HttpLoaderFactory(http: Http) {
|
||||
BrowserDataLoaderModule,
|
||||
BrowserTransferStateModule,
|
||||
BrowserTransferStoreModule,
|
||||
EffectsModule.run(BrowserTransferStoreEffects),
|
||||
EffectsModule.forRoot([BrowserTransferStoreEffects]),
|
||||
StoreRouterConnectingModule,
|
||||
BrowserAnimationsModule,
|
||||
AppModule
|
||||
],
|
||||
@@ -68,6 +71,10 @@ export function HttpLoaderFactory(http: Http) {
|
||||
deps: [
|
||||
TransferState
|
||||
]
|
||||
},
|
||||
{
|
||||
provide: RouterStateSerializer,
|
||||
useClass: DSpaceRouterStateSerializer
|
||||
}
|
||||
]
|
||||
})
|
||||
|
@@ -1,12 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { CacheableObject } from '../object-cache.reducer';
|
||||
import { ObjectCacheService } from '../object-cache.service';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { ResponseCacheService } from '../response-cache.service';
|
||||
import { CoreState } from '../../core.reducers';
|
||||
import { RequestEntry } from '../../data/request.reducer';
|
||||
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { ResponseCacheEntry } from '../response-cache.reducer';
|
||||
@@ -20,10 +18,12 @@ import { PageInfo } from '../../shared/page-info.model';
|
||||
|
||||
@Injectable()
|
||||
export class RemoteDataBuildService {
|
||||
constructor(protected objectCache: ObjectCacheService,
|
||||
protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
protected store: Store<CoreState>,) {
|
||||
constructor(
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService
|
||||
|
||||
) {
|
||||
}
|
||||
|
||||
buildSingle<TNormalized extends CacheableObject, TDomain>(href: string,
|
||||
@@ -31,9 +31,9 @@ export class RemoteDataBuildService {
|
||||
const requestHrefObs = this.objectCache.getRequestHrefBySelfLink(href);
|
||||
|
||||
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) =>
|
||||
this.store.select<RequestEntry>('core', 'data', 'request', requestHref)).filter((entry) => hasValue(entry))
|
||||
this.requestService.get(requestHref)).filter((entry) => hasValue(entry))
|
||||
);
|
||||
|
||||
const responseCacheObs = Observable.race(
|
||||
@@ -97,7 +97,7 @@ export class RemoteDataBuildService {
|
||||
).filter((normalized) => hasValue(normalized))
|
||||
.map((normalized: TNormalized) => {
|
||||
return this.build<TNormalized, TDomain>(normalized);
|
||||
});
|
||||
}).distinctUntilChanged();
|
||||
|
||||
return new RemoteData(
|
||||
href,
|
||||
@@ -113,7 +113,7 @@ export class RemoteDataBuildService {
|
||||
|
||||
buildList<TNormalized extends CacheableObject, TDomain>(href: string,
|
||||
normalizedType: GenericConstructor<TNormalized>): RemoteData<TDomain[]> {
|
||||
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', href)
|
||||
const requestObs = this.requestService.get(href)
|
||||
.filter((entry) => hasValue(entry));
|
||||
const responseCacheObs = this.responseCache.get(href).filter((entry) => hasValue(entry));
|
||||
|
||||
|
18
src/app/core/cache/cache.reducers.ts
vendored
18
src/app/core/cache/cache.reducers.ts
vendored
@@ -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);
|
||||
}
|
4
src/app/core/cache/object-cache.reducer.ts
vendored
4
src/app/core/cache/object-cache.reducer.ts
vendored
@@ -48,7 +48,7 @@ const initialState: ObjectCacheState = Object.create(null);
|
||||
* @return ObjectCacheState
|
||||
* the new state
|
||||
*/
|
||||
export const objectCacheReducer = (state = initialState, action: ObjectCacheAction): ObjectCacheState => {
|
||||
export function objectCacheReducer(state = initialState, action: ObjectCacheAction): ObjectCacheState {
|
||||
switch (action.type) {
|
||||
|
||||
case ObjectCacheActionTypes.ADD: {
|
||||
@@ -67,7 +67,7 @@ export const objectCacheReducer = (state = initialState, action: ObjectCacheActi
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object to the cache
|
||||
|
@@ -2,8 +2,9 @@ import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ObjectCacheService } from './object-cache.service';
|
||||
import { ObjectCacheState, CacheableObject } from './object-cache.reducer';
|
||||
import { CacheableObject } from './object-cache.reducer';
|
||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions';
|
||||
import { CoreState } from '../core.reducers';
|
||||
|
||||
class TestClass implements CacheableObject {
|
||||
constructor(
|
||||
@@ -18,7 +19,7 @@ class TestClass implements CacheableObject {
|
||||
|
||||
describe('ObjectCacheService', () => {
|
||||
let service: ObjectCacheService;
|
||||
let store: Store<ObjectCacheState>;
|
||||
let store: Store<CoreState>;
|
||||
|
||||
const uuid = '1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
|
||||
const requestHref = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
|
||||
@@ -36,7 +37,7 @@ describe('ObjectCacheService', () => {
|
||||
const invalidCacheEntry = Object.assign({}, cacheEntry, { msToLive: -1 });
|
||||
|
||||
beforeEach(() => {
|
||||
store = new Store<ObjectCacheState>(undefined, undefined, undefined);
|
||||
store = new Store<CoreState>(undefined, undefined, undefined);
|
||||
spyOn(store, 'dispatch');
|
||||
service = new ObjectCacheService(store);
|
||||
|
||||
|
26
src/app/core/cache/object-cache.service.ts
vendored
26
src/app/core/cache/object-cache.service.ts
vendored
@@ -1,12 +1,22 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { MemoizedSelector, Store } from '@ngrx/store';
|
||||
|
||||
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 { hasNoValue } from '../../shared/empty.util';
|
||||
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
|
||||
@@ -14,7 +24,7 @@ import { GenericConstructor } from '../shared/generic-constructor';
|
||||
@Injectable()
|
||||
export class ObjectCacheService {
|
||||
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> {
|
||||
return this.store.select<string>('core', 'index', 'href', href)
|
||||
return this.store.select(uuidFromHrefSelector(href))
|
||||
.flatMap((uuid: string) => this.get(uuid, type))
|
||||
}
|
||||
|
||||
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))
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
@@ -82,7 +92,7 @@ export class ObjectCacheService {
|
||||
}
|
||||
|
||||
getRequestHrefBySelfLink(self: string): Observable<string> {
|
||||
return this.store.select<string>('core', 'index', 'href', self)
|
||||
return this.store.select(uuidFromHrefSelector(self))
|
||||
.flatMap((uuid: string) => this.getRequestHref(uuid));
|
||||
}
|
||||
|
||||
@@ -123,7 +133,7 @@ export class ObjectCacheService {
|
||||
has(uuid: string): boolean {
|
||||
let result: boolean;
|
||||
|
||||
this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
|
||||
this.store.select(objectFromUuidSelector(uuid))
|
||||
.take(1)
|
||||
.subscribe((entry) => result = this.isValid(entry));
|
||||
|
||||
@@ -142,7 +152,7 @@ export class ObjectCacheService {
|
||||
hasBySelfLink(href: string): boolean {
|
||||
let result = false;
|
||||
|
||||
this.store.select<string>('core', 'index', 'href', href)
|
||||
this.store.select(uuidFromHrefSelector(href))
|
||||
.take(1)
|
||||
.subscribe((uuid: string) => result = this.has(uuid));
|
||||
|
||||
|
4
src/app/core/cache/response-cache.reducer.ts
vendored
4
src/app/core/cache/response-cache.reducer.ts
vendored
@@ -37,7 +37,7 @@ const initialState = Object.create(null);
|
||||
* @return ResponseCacheState
|
||||
* the new state
|
||||
*/
|
||||
export const responseCacheReducer = (state = initialState, action: ResponseCacheAction): ResponseCacheState => {
|
||||
export function responseCacheReducer(state = initialState, action: ResponseCacheAction): ResponseCacheState {
|
||||
switch (action.type) {
|
||||
|
||||
case ResponseCacheActionTypes.ADD: {
|
||||
@@ -56,7 +56,7 @@ export const responseCacheReducer = (state = initialState, action: ResponseCache
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function addToCache(state: ResponseCacheState, action: ResponseCacheAddAction): ResponseCacheState {
|
||||
return Object.assign({}, state, {
|
||||
|
21
src/app/core/cache/response-cache.service.ts
vendored
21
src/app/core/cache/response-cache.service.ts
vendored
@@ -1,12 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { MemoizedSelector, Store } from '@ngrx/store';
|
||||
|
||||
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 { ResponseCacheRemoveAction, ResponseCacheAddAction } from './response-cache.actions';
|
||||
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/response', key);
|
||||
}
|
||||
|
||||
/**
|
||||
* A service to interact with the response cache
|
||||
@@ -14,7 +20,7 @@ import { Response } from './response-cache.models';
|
||||
@Injectable()
|
||||
export class ResponseCacheService {
|
||||
constructor(
|
||||
private store: Store<ResponseCacheState>
|
||||
private store: Store<CoreState>
|
||||
) { }
|
||||
|
||||
add(key: string, response: Response, msToLive: number): Observable<ResponseCacheEntry> {
|
||||
@@ -34,8 +40,9 @@ export class ResponseCacheService {
|
||||
* an observable of the ResponseCacheEntry with the specified key
|
||||
*/
|
||||
get(key: string): Observable<ResponseCacheEntry> {
|
||||
return this.store.select<ResponseCacheEntry>('core', 'cache', 'response', key)
|
||||
.filter((entry) => this.isValid(entry))
|
||||
return this.store.select(entryFromKeySelector(key))
|
||||
.filter((entry: ResponseCacheEntry) => this.isValid(entry))
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,9 +57,9 @@ export class ResponseCacheService {
|
||||
has(key: string): boolean {
|
||||
let result: boolean;
|
||||
|
||||
this.store.select<ResponseCacheEntry>('core', 'cache', 'response', key)
|
||||
this.store.select(entryFromKeySelector(key))
|
||||
.take(1)
|
||||
.subscribe((entry) => {
|
||||
.subscribe((entry: ResponseCacheEntry) => {
|
||||
result = this.isValid(entry);
|
||||
});
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { ObjectCacheEffects } from './data/object-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';
|
||||
|
||||
export const coreEffects = [
|
||||
EffectsModule.run(RequestEffects),
|
||||
EffectsModule.run(ObjectCacheEffects),
|
||||
EffectsModule.run(HrefIndexEffects),
|
||||
RequestCacheEffects,
|
||||
RequestEffects,
|
||||
ObjectCacheEffects,
|
||||
HrefIndexEffects,
|
||||
];
|
||||
|
@@ -14,10 +14,16 @@ import { RequestService } from './data/request.service';
|
||||
import { RemoteDataBuildService } from './cache/builders/remote-data-build.service';
|
||||
import { CommunityDataService } from './data/community-data.service';
|
||||
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 = [
|
||||
CommonModule,
|
||||
SharedModule
|
||||
SharedModule,
|
||||
StoreModule.forFeature('core', coreReducers, { }),
|
||||
EffectsModule.forFeature(coreEffects)
|
||||
];
|
||||
|
||||
const DECLARATIONS = [
|
||||
|
@@ -1,21 +1,22 @@
|
||||
import { combineReducers } from '@ngrx/store';
|
||||
import { ActionReducerMap, createFeatureSelector } from '@ngrx/store';
|
||||
|
||||
import { CacheState, cacheReducer } from './cache/cache.reducers';
|
||||
import { IndexState, indexReducer } from './index/index.reducers';
|
||||
import { DataState, dataReducer } from './data/data.reducers';
|
||||
import { responseCacheReducer, ResponseCacheState } from './cache/response-cache.reducer';
|
||||
import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reducer';
|
||||
import { hrefIndexReducer, HrefIndexState } from './index/href-index.reducer';
|
||||
import { requestReducer, RequestState } from './data/request.reducer';
|
||||
|
||||
export interface CoreState {
|
||||
cache: CacheState,
|
||||
index: IndexState,
|
||||
data: DataState
|
||||
'data/object': ObjectCacheState,
|
||||
'data/response': ResponseCacheState,
|
||||
'data/request': RequestState,
|
||||
'index/href': HrefIndexState
|
||||
}
|
||||
|
||||
export const reducers = {
|
||||
cache: cacheReducer,
|
||||
index: indexReducer,
|
||||
data: dataReducer
|
||||
export const coreReducers: ActionReducerMap<CoreState> = {
|
||||
'data/object': objectCacheReducer,
|
||||
'data/response': responseCacheReducer,
|
||||
'data/request': requestReducer,
|
||||
'index/href': hrefIndexReducer
|
||||
};
|
||||
|
||||
export function coreReducer(state: any, action: any) {
|
||||
return combineReducers(reducers)(state, action);
|
||||
}
|
||||
export const coreSelector = createFeatureSelector<CoreState>('core');
|
||||
|
@@ -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);
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
|
||||
import { ObjectCacheActionTypes } from '../cache/object-cache.actions';
|
||||
import { ResetResponseCacheTimestampsAction } from '../cache/response-cache.actions';
|
||||
import { StoreActionTypes } from '../../store.actions';
|
||||
|
||||
@Injectable()
|
||||
export class RequestCacheEffects {
|
||||
@@ -14,17 +14,9 @@ export class RequestCacheEffects {
|
||||
*
|
||||
* This assumes that the server cached everything a negligible
|
||||
* time ago, and will likely need to be revisited later
|
||||
*
|
||||
* This effect should listen for StoreActionTypes.REHYDRATE,
|
||||
* but can't because you can only have one effect listen to
|
||||
* an action atm. Github issue:
|
||||
* https://github.com/ngrx/effects/issues/87
|
||||
*
|
||||
* It's listening for ObjectCacheActionTypes.RESET_TIMESTAMPS
|
||||
* instead, until there's a solution.
|
||||
*/
|
||||
@Effect() fixTimestampsOnRehydrate = this.actions$
|
||||
.ofType(ObjectCacheActionTypes.RESET_TIMESTAMPS)
|
||||
.ofType(StoreActionTypes.REHYDRATE)
|
||||
.map(() => new ResetResponseCacheTimestampsAction(new Date().getTime()));
|
||||
|
||||
constructor(private actions$: Actions, ) { }
|
||||
|
@@ -19,7 +19,7 @@ export interface RequestState {
|
||||
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
||||
const initialState = Object.create(null);
|
||||
|
||||
export const requestReducer = (state = initialState, action: RequestAction): RequestState => {
|
||||
export function requestReducer(state = initialState, action: RequestAction): RequestState {
|
||||
switch (action.type) {
|
||||
|
||||
case RequestActionTypes.CONFIGURE: {
|
||||
@@ -38,7 +38,7 @@ export const requestReducer = (state = initialState, action: RequestAction): Req
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function configureRequest(state: RequestState, action: RequestConfigureAction): RequestState {
|
||||
return Object.assign({}, state, {
|
||||
|
@@ -1,12 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { request } from 'http';
|
||||
import { MemoizedSelector, Store } from '@ngrx/store';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { RequestEntry, RequestState } from './request.reducer';
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { Request } from './request.models';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
|
||||
@@ -15,6 +13,12 @@ import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
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()
|
||||
export class RequestService {
|
||||
@@ -22,13 +26,13 @@ export class RequestService {
|
||||
constructor(
|
||||
private objectCache: ObjectCacheService,
|
||||
private responseCache: ResponseCacheService,
|
||||
private store: Store<RequestState>
|
||||
private store: Store<CoreState>
|
||||
) {
|
||||
}
|
||||
|
||||
isPending(href: string): boolean {
|
||||
let isPending = false;
|
||||
this.store.select<RequestEntry>('core', 'data', 'request', href)
|
||||
this.store.select(entryFromHrefSelector(href))
|
||||
.take(1)
|
||||
.subscribe((re: RequestEntry) => {
|
||||
isPending = (hasValue(re) && !re.completed)
|
||||
@@ -38,7 +42,7 @@ export class RequestService {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@@ -33,7 +33,7 @@ describe('Footer component', () => {
|
||||
// async beforeEach
|
||||
beforeEach(async(() => {
|
||||
return TestBed.configureTestingModule({
|
||||
imports: [CommonModule, StoreModule.provideStore({}), TranslateModule.forRoot({
|
||||
imports: [CommonModule, StoreModule.forRoot({}), TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: MockTranslateLoader
|
||||
|
@@ -12,7 +12,7 @@ export interface HrefIndexState {
|
||||
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
||||
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) {
|
||||
|
||||
case HrefIndexActionTypes.ADD: {
|
||||
@@ -27,7 +27,7 @@ export const hrefIndexReducer = (state = initialState, action: HrefIndexAction):
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function addToHrefIndex(state: HrefIndexState, action: AddToHrefIndexAction): HrefIndexState {
|
||||
return Object.assign({}, state, {
|
||||
|
@@ -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);
|
||||
}
|
13
src/app/core/shared/selectors.ts
Normal file
13
src/app/core/shared/selectors.ts
Normal 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;
|
||||
}
|
||||
});
|
||||
}
|
@@ -21,7 +21,7 @@ describe('HeaderComponent', () => {
|
||||
// async beforeEach
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [StoreModule.provideStore({}), TranslateModule.forRoot(), NgbCollapseModule.forRoot()],
|
||||
imports: [StoreModule.forRoot({}), TranslateModule.forRoot(), NgbCollapseModule.forRoot()],
|
||||
declarations: [HeaderComponent]
|
||||
})
|
||||
.compileComponents(); // compile template and css
|
||||
@@ -70,7 +70,7 @@ describe('HeaderComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
menu = fixture.debugElement.query(By.css('#collapsingNav')).nativeElement;
|
||||
spyOn(store, 'select').and.returnValue(Observable.of({ navCollapsed: false }));
|
||||
spyOn(store, 'select').and.returnValue(Observable.of(false));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
@@ -1,9 +1,13 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { createSelector, Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { HeaderState } from './header.reducer';
|
||||
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({
|
||||
selector: 'ds-header',
|
||||
@@ -14,14 +18,12 @@ export class HeaderComponent implements OnInit {
|
||||
public isNavBarCollapsed: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private store: Store<HeaderState>
|
||||
private store: Store<AppState>
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isNavBarCollapsed = this.store.select('header')
|
||||
// unwrap navCollapsed
|
||||
.map(({ navCollapsed }: HeaderState) => navCollapsed);
|
||||
this.isNavBarCollapsed = this.store.select(navCollapsedSelector);
|
||||
}
|
||||
|
||||
public toggle(): void {
|
||||
|
@@ -1,41 +1,36 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { EffectsTestingModule, EffectsRunner } from '@ngrx/effects/testing';
|
||||
import { routerActions } from '@ngrx/router-store';
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { HeaderEffects } from './header.effects';
|
||||
import { HeaderCollapseAction } from './header.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', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
imports: [
|
||||
EffectsTestingModule
|
||||
],
|
||||
providers: [
|
||||
HeaderEffects
|
||||
]
|
||||
}));
|
||||
|
||||
let runner: EffectsRunner;
|
||||
let headerEffects: HeaderEffects;
|
||||
let actions: Observable<any>;
|
||||
|
||||
beforeEach(inject([
|
||||
EffectsRunner, HeaderEffects
|
||||
],
|
||||
(_runner, _headerEffects) => {
|
||||
runner = _runner;
|
||||
headerEffects = _headerEffects;
|
||||
}
|
||||
));
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
HeaderEffects,
|
||||
provideMockActions(() => actions),
|
||||
// other providers
|
||||
],
|
||||
});
|
||||
|
||||
headerEffects = TestBed.get(HeaderEffects);
|
||||
});
|
||||
|
||||
describe('resize$', () => {
|
||||
|
||||
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) => {
|
||||
expect(result).toEqual(new HeaderCollapseAction());
|
||||
});
|
||||
const expected = cold('--b-', { b: new HeaderCollapseAction() });
|
||||
|
||||
expect(headerEffects.resize$).toBeObservable(expected);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -43,11 +38,11 @@ describe('HeaderEffects', () => {
|
||||
describe('routeChange$', () => {
|
||||
|
||||
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) => {
|
||||
expect(result).toEqual(new HeaderCollapseAction());
|
||||
});
|
||||
const expected = cold('--b-', { b: new HeaderCollapseAction() });
|
||||
|
||||
expect(headerEffects.routeChange$).toBeObservable(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
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 { HeaderCollapseAction } from './header.actions';
|
||||
@@ -13,7 +13,7 @@ export class HeaderEffects {
|
||||
.map(() => new HeaderCollapseAction());
|
||||
|
||||
@Effect() routeChange$ = this.actions$
|
||||
.ofType(routerActions.UPDATE_LOCATION)
|
||||
.ofType(fromRouter.ROUTER_NAVIGATION)
|
||||
.map(() => new HeaderCollapseAction());
|
||||
|
||||
constructor(private actions$: Actions) {
|
||||
|
@@ -8,7 +8,7 @@ const initialState: HeaderState = {
|
||||
navCollapsed: true
|
||||
};
|
||||
|
||||
export const headerReducer = (state = initialState, action: HeaderAction): HeaderState => {
|
||||
export function headerReducer(state = initialState, action: HeaderAction): HeaderState {
|
||||
switch (action.type) {
|
||||
|
||||
case HeaderActionTypes.COLLAPSE: {
|
||||
@@ -35,4 +35,4 @@ export const headerReducer = (state = initialState, action: HeaderAction): Heade
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<ds-metadata-field-wrapper [label]="label | translate">
|
||||
<ds-metadata-field-wrapper *ngIf="(files | async)?.length > 0" [label]="label | translate">
|
||||
<div class="file-section">
|
||||
<a *ngFor="let file of (files | async); let last=last;" [href]="file?.content" [download]="file?.name">
|
||||
<span>{{file?.name}}</span>
|
||||
|
@@ -1,12 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ServerResponseService } from '../shared/server-response.service';
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-pagenotfound',
|
||||
styleUrls: ['./pagenotfound.component.scss'],
|
||||
templateUrl: './pagenotfound.component.html'
|
||||
templateUrl: './pagenotfound.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PageNotFoundComponent {
|
||||
|
||||
data: any = {};
|
||||
|
||||
constructor(responseService: ServerResponseService) {
|
||||
responseService.setNotFound();
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import 'rxjs/add/operator/filter';
|
||||
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 { ServerModule } from '@angular/platform-server';
|
||||
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 { Store } from '@ngrx/store';
|
||||
import { Actions, EffectsModule } from '@ngrx/effects';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { TranslateUniversalLoader } from '../modules/translate-universal-loader';
|
||||
|
||||
import { ServerTransferStateModule } from '../modules/transfer-state/server-transfer-state.module';
|
||||
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 { ServerTransferStoreModule } from '../modules/transfer-store/server-transfer-store.module';
|
||||
|
||||
@@ -32,15 +31,14 @@ import { ServerCookiesModule } from '../modules/cookies/server-cookies.module';
|
||||
import { ServerDataLoaderModule } from '../modules/data-loader/server-data-loader.module';
|
||||
|
||||
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 { AppComponent } from './app.component';
|
||||
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../config';
|
||||
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||
import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer';
|
||||
|
||||
export function boot(cache: TransferState, appRef: ApplicationRef, store: Store<AppState>, request: Request, config: GlobalConfig) {
|
||||
// authentication mechanism goes here
|
||||
@@ -61,6 +59,7 @@ export function UniversalLoaderFactory() {
|
||||
appId: 'ds-app-id'
|
||||
}),
|
||||
RouterModule.forRoot([], { useHash: false }),
|
||||
StoreRouterConnectingModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
@@ -74,7 +73,7 @@ export function UniversalLoaderFactory() {
|
||||
ServerDataLoaderModule,
|
||||
ServerTransferStateModule,
|
||||
ServerTransferStoreModule,
|
||||
EffectsModule.run(ServerTransferStoreEffects),
|
||||
EffectsModule.forRoot([ServerTransferStoreEffects]),
|
||||
NoopAnimationsModule,
|
||||
AppModule
|
||||
],
|
||||
@@ -90,6 +89,10 @@ export function UniversalLoaderFactory() {
|
||||
REQUEST,
|
||||
GLOBAL_CONFIG
|
||||
]
|
||||
},
|
||||
{
|
||||
provide: RouterStateSerializer,
|
||||
useClass: DSpaceRouterStateSerializer
|
||||
}
|
||||
]
|
||||
})
|
||||
|
@@ -10,7 +10,7 @@ const initialState: HostWindowState = {
|
||||
height: null
|
||||
};
|
||||
|
||||
export const hostWindowReducer = (state = initialState, action: HostWindowAction): HostWindowState => {
|
||||
export function hostWindowReducer(state = initialState, action: HostWindowAction): HostWindowState {
|
||||
switch (action.type) {
|
||||
|
||||
case HostWindowActionTypes.RESIZE: {
|
||||
@@ -21,4 +21,4 @@ export const hostWindowReducer = (state = initialState, action: HostWindowAction
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -1,17 +1,18 @@
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { AppState } from '../app.reducer';
|
||||
import { HostWindowState } from './host-window.reducer';
|
||||
|
||||
import { HostWindowService } from './host-window.service';
|
||||
import { HostWindowState } from './host-window.reducer';
|
||||
|
||||
describe('HostWindowService', () => {
|
||||
let service: HostWindowService;
|
||||
let store: Store<HostWindowState>;
|
||||
let store: Store<AppState>;
|
||||
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
const _initialState = { width: 1600, height: 770 };
|
||||
store = new Store<HostWindowState>(undefined, undefined, Observable.of(_initialState));
|
||||
const _initialState = { hostWindow: { width: 1600, height: 770 } };
|
||||
store = new Store<AppState>(Observable.of(_initialState), undefined, undefined);
|
||||
service = new HostWindowService(store);
|
||||
});
|
||||
|
||||
@@ -46,8 +47,8 @@ describe('HostWindowService', () => {
|
||||
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
const _initialState = { width: 1100, height: 770 };
|
||||
store = new Store<HostWindowState>(undefined, undefined, Observable.of(_initialState));
|
||||
const _initialState = { hostWindow: { width: 1100, height: 770 } };
|
||||
store = new Store<AppState>(Observable.of(_initialState), undefined, undefined);
|
||||
service = new HostWindowService(store);
|
||||
});
|
||||
|
||||
@@ -82,8 +83,8 @@ describe('HostWindowService', () => {
|
||||
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
const _initialState = { width: 800, height: 770 };
|
||||
store = new Store<HostWindowState>(undefined, undefined, Observable.of(_initialState));
|
||||
const _initialState = { hostWindow: { width: 800, height: 770 } };
|
||||
store = new Store<AppState>(Observable.of(_initialState), undefined, undefined);
|
||||
service = new HostWindowService(store);
|
||||
});
|
||||
|
||||
@@ -118,8 +119,8 @@ describe('HostWindowService', () => {
|
||||
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
const _initialState = { width: 600, height: 770 };
|
||||
store = new Store<HostWindowState>(undefined, undefined, Observable.of(_initialState));
|
||||
const _initialState = { hostWindow: { width: 600, height: 770 } };
|
||||
store = new Store<AppState>(Observable.of(_initialState), undefined, undefined);
|
||||
service = new HostWindowService(store);
|
||||
});
|
||||
|
||||
@@ -154,8 +155,8 @@ describe('HostWindowService', () => {
|
||||
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
const _initialState = { width: 400, height: 770 };
|
||||
store = new Store<HostWindowState>(undefined, undefined, Observable.of(_initialState));
|
||||
const _initialState = { hostWindow: { width: 400, height: 770 } };
|
||||
store = new Store<AppState>(Observable.of(_initialState), undefined, undefined);
|
||||
service = new HostWindowService(store);
|
||||
});
|
||||
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { HostWindowState } from './host-window.reducer';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { createSelector, Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { hasValue } from './empty.util';
|
||||
import { AppState } from '../app.reducer';
|
||||
|
||||
// TODO: ideally we should get these from sass somehow
|
||||
export enum GridBreakpoint {
|
||||
@@ -14,16 +15,19 @@ export enum GridBreakpoint {
|
||||
XL = 1200
|
||||
}
|
||||
|
||||
const hostWindowStateSelector = (state: AppState) => state.hostWindow;
|
||||
const widthSelector = createSelector(hostWindowStateSelector, (hostWindow: HostWindowState) => hostWindow.width);
|
||||
|
||||
@Injectable()
|
||||
export class HostWindowService {
|
||||
|
||||
constructor(
|
||||
private store: Store<HostWindowState>
|
||||
private store: Store<AppState>
|
||||
) {
|
||||
}
|
||||
|
||||
private getWidthObs(): Observable<number> {
|
||||
return this.store.select<number>('hostWindow', 'width')
|
||||
return this.store.select(widthSelector)
|
||||
.filter((width) => hasValue(width));
|
||||
}
|
||||
|
||||
|
18
src/app/shared/ngrx/dspace-router-state-serializer.ts
Normal file
18
src/app/shared/ngrx/dspace-router-state-serializer.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { RouterStateSerializer } from '@ngrx/router-store';
|
||||
import { Params, RouterStateSnapshot } from '@angular/router';
|
||||
|
||||
export interface RouterStateUrl {
|
||||
url: string;
|
||||
queryParams: Params;
|
||||
}
|
||||
|
||||
export class DSpaceRouterStateSerializer implements RouterStateSerializer<RouterStateUrl> {
|
||||
serialize(routerState: RouterStateSnapshot): RouterStateUrl {
|
||||
const { url } = routerState;
|
||||
const queryParams = routerState.root.queryParams;
|
||||
|
||||
// Only return an object including the URL and query params
|
||||
// instead of the entire snapshot
|
||||
return { url, queryParams };
|
||||
}
|
||||
}
|
@@ -140,7 +140,7 @@ describe('Pagination component', () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
StoreModule.provideStore({}),
|
||||
StoreModule.forRoot({}),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
|
26
src/app/shared/server-response.service.ts
Normal file
26
src/app/shared/server-response.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { RESPONSE } from '@nguniversal/express-engine/tokens';
|
||||
import { Inject, Injectable, Optional } from '@angular/core';
|
||||
import { Response } from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class ServerResponseService {
|
||||
private response: Response;
|
||||
|
||||
constructor(@Optional() @Inject(RESPONSE) response: any) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
setStatus(code: number, message?: string): this {
|
||||
if (this.response) {
|
||||
this.response.statusCode = code;
|
||||
if (message) {
|
||||
this.response.statusMessage = message;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
setNotFound(message = 'Not found'): this {
|
||||
return this.setStatus(404, message)
|
||||
}
|
||||
}
|
@@ -27,6 +27,7 @@ import { TruncatePipe } from './utils/truncate.pipe';
|
||||
import { WrapperListElementComponent } from '../object-list/wrapper-list-element/wrapper-list-element.component';
|
||||
import { SearchResultListElementComponent } from '../object-list/search-result-list-element/search-result-list-element.component';
|
||||
import { SearchFormComponent } from './search-form/search-form.component';
|
||||
import { ServerResponseService } from './server-response.service';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -71,7 +72,8 @@ const ENTRY_COMPONENTS = [
|
||||
const PROVIDERS = [
|
||||
ApiService,
|
||||
HostWindowService,
|
||||
{ provide: NativeWindowService, useFactory: NativeWindowFactory }
|
||||
{ provide: NativeWindowService, useFactory: NativeWindowFactory },
|
||||
ServerResponseService
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@@ -8,6 +8,7 @@ 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 {
|
||||
|
48
yarn.lock
48
yarn.lock
@@ -90,25 +90,21 @@
|
||||
version "1.0.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-1.0.0-beta.1.tgz#a7d5935293df22a2275bf572f2197b45136e3c52"
|
||||
|
||||
"@ngrx/core@1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@ngrx/core/-/core-1.2.0.tgz#882b46abafa2e0e6d887cb71a1b2c2fa3e6d0dc6"
|
||||
"@ngrx/effects@^4.0.5":
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@ngrx/effects/-/effects-4.0.5.tgz#1224763800621b7305f9b18bc17ee09b25c861d1"
|
||||
|
||||
"@ngrx/effects@2.0.4":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@ngrx/effects/-/effects-2.0.4.tgz#418eee5e1032fa66de5bbf1855653bb1951f12a4"
|
||||
"@ngrx/router-store@^4.0.4":
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@ngrx/router-store/-/router-store-4.0.4.tgz#ab59f35aae93465088384faf009e21b22edd456a"
|
||||
|
||||
"@ngrx/router-store@1.2.6":
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@ngrx/router-store/-/router-store-1.2.6.tgz#a2eb0ca515e9b367781f1030250dd64bb73c086b"
|
||||
"@ngrx/store-devtools@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@ngrx/store-devtools/-/store-devtools-4.0.0.tgz#b79c24773217df7fd9735ad21f9cbf2533c96e04"
|
||||
|
||||
"@ngrx/store-devtools@3.2.4":
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@ngrx/store-devtools/-/store-devtools-3.2.4.tgz#2ce4d13bf34848a9e51ec87e3b125ed67b51e550"
|
||||
|
||||
"@ngrx/store@2.2.3":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-2.2.3.tgz#e7bd1149f1c44208f1cc4744353f0f98a0f1f57b"
|
||||
"@ngrx/store@^4.0.3":
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-4.0.3.tgz#36abacdfa19bfb8506e40de80bae06050a1e15e9"
|
||||
|
||||
"@ngtools/webpack@1.5.1":
|
||||
version "1.5.1"
|
||||
@@ -1731,10 +1727,6 @@ deep-extend@~0.4.0:
|
||||
version "0.4.2"
|
||||
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:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84"
|
||||
@@ -3487,6 +3479,12 @@ jasmine-core@2.6.4, jasmine-core@~2.6.0:
|
||||
version "2.6.4"
|
||||
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:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-spec-reporter/-/jasmine-spec-reporter-4.1.1.tgz#5a6d58ab5d61bea7309fbc279239511756b1b588"
|
||||
@@ -3936,6 +3934,10 @@ lodash.isarray@^3.0.0:
|
||||
version "3.0.4"
|
||||
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:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
|
||||
@@ -4359,12 +4361,6 @@ nested-error-stacks@^1.0.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ngx-pagination/-/ngx-pagination-3.0.1.tgz#5a8000e40c0424d9c41c9d6d592562e1547abf24"
|
||||
|
Reference in New Issue
Block a user