mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Refactored Data Services
This commit is contained in:
@@ -103,6 +103,7 @@
|
||||
"ng2-translate": "4.2.0",
|
||||
"preboot": "4.5.2",
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
"ts-md5": "^1.2.0",
|
||||
"webfontloader": "1.6.27",
|
||||
"zone.js": "0.6.26"
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { EffectsModule } from "@ngrx/effects";
|
||||
import { CollectionDataEffects } from "./data-services/collection/collection-data.effects";
|
||||
import { ItemDataEffects } from "./data-services/item/item-data.effects";
|
||||
import { CollectionDataEffects } from "./data-services/collection-data.effects";
|
||||
import { ItemDataEffects } from "./data-services/item-data.effects";
|
||||
|
||||
export const coreEffects = [
|
||||
EffectsModule.run(CollectionDataEffects),
|
||||
|
@@ -4,9 +4,9 @@ import { SharedModule } from "../shared/shared.module";
|
||||
import { isNotEmpty } from "../shared/empty.util";
|
||||
import { FooterComponent } from "./footer/footer.component";
|
||||
import { DSpaceRESTv2Service } from "./dspace-rest-v2/dspace-rest-v2.service";
|
||||
import { CollectionDataService } from "./data-services/collection/collection-data.service";
|
||||
import { CacheService } from "./data-services/cache/cache.service";
|
||||
import { ItemDataService } from "./data-services/item/item-data.service";
|
||||
import { CollectionDataService } from "./data-services/collection-data.service";
|
||||
import { ItemDataService } from "./data-services/item-data.service";
|
||||
|
||||
const IMPORTS = [
|
||||
CommonModule,
|
||||
|
@@ -1,20 +1,14 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import {
|
||||
CollectionDataState,
|
||||
collectionDataReducer
|
||||
} from "./data-services/collection/collection-data.reducer";
|
||||
import { CacheState, cacheReducer } from "./data-services/cache/cache.reducer";
|
||||
import { ItemDataState, itemDataReducer } from "./data-services/item/item-data.reducer";
|
||||
import { dataReducer, DataState } from "./data-services/data.reducer";
|
||||
|
||||
export interface CoreState {
|
||||
collectionData: CollectionDataState,
|
||||
itemData: ItemDataState,
|
||||
data: DataState,
|
||||
cache: CacheState
|
||||
}
|
||||
|
||||
export const reducers = {
|
||||
collectionData: collectionDataReducer,
|
||||
itemData: itemDataReducer,
|
||||
data: dataReducer,
|
||||
cache: cacheReducer
|
||||
};
|
||||
|
||||
|
38
src/app/core/data-services/collection-data.effects.ts
Normal file
38
src/app/core/data-services/collection-data.effects.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { DataEffects } from "./data.effects";
|
||||
import { Serializer } from "../serializer";
|
||||
import { Collection } from "../shared/collection.model";
|
||||
import { DSpaceRESTv2Serializer } from "../dspace-rest-v2/dspace-rest-v2.serializer";
|
||||
import { CacheService } from "./cache/cache.service";
|
||||
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { DataFindAllRequestAction, DataFindByIDRequestAction } from "./data.actions";
|
||||
import { CollectionDataService } from "./collection-data.service";
|
||||
|
||||
@Injectable()
|
||||
export class CollectionDataEffects extends DataEffects<Collection> {
|
||||
constructor(
|
||||
actions$: Actions,
|
||||
restApi: DSpaceRESTv2Service,
|
||||
cache: CacheService,
|
||||
dataService: CollectionDataService
|
||||
) {
|
||||
super(actions$, restApi, cache, dataService);
|
||||
}
|
||||
|
||||
protected getFindAllEndpoint(action: DataFindAllRequestAction): string {
|
||||
return '/collections';
|
||||
}
|
||||
|
||||
protected getFindByIdEndpoint(action: DataFindByIDRequestAction): string {
|
||||
return `/collections/${action.payload.resourceID}`;
|
||||
}
|
||||
|
||||
protected getSerializer(): Serializer<Collection> {
|
||||
return new DSpaceRESTv2Serializer(Collection);
|
||||
}
|
||||
|
||||
@Effect() findAll$ = this.findAll;
|
||||
|
||||
@Effect() findById$ = this.findById;
|
||||
}
|
19
src/app/core/data-services/collection-data.service.ts
Normal file
19
src/app/core/data-services/collection-data.service.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable, OpaqueToken } from "@angular/core";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { DataService } from "./data.service";
|
||||
import { Collection } from "../shared/collection.model";
|
||||
import { CacheService } from "./cache/cache.service";
|
||||
import { DataState } from "./data.reducer";
|
||||
|
||||
@Injectable()
|
||||
export class CollectionDataService extends DataService<Collection> {
|
||||
name = new OpaqueToken('CollectionDataService');
|
||||
|
||||
constructor(
|
||||
store: Store<DataState>,
|
||||
cache: CacheService
|
||||
) {
|
||||
super(store, cache);
|
||||
}
|
||||
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { Collection } from "../../shared/collection.model";
|
||||
import { Observable } from "rxjs";
|
||||
import {
|
||||
CollectionFindMultipleActionTypes,
|
||||
CollectionFindMultipleSuccessAction,
|
||||
CollectionFindMultipleErrorAction
|
||||
} from "./collection-find-multiple.actions";
|
||||
import {
|
||||
CollectionFindSingleActionTypes,
|
||||
CollectionFindByIdSuccessAction,
|
||||
CollectionFindByIdErrorAction
|
||||
} from "./collection-find-single.actions";
|
||||
import { DSpaceRESTV2Response } from "../../dspace-rest-v2/dspace-rest-v2-response.model";
|
||||
import { DSpaceRESTv2Serializer } from "../../dspace-rest-v2/dspace-rest-v2.serializer";
|
||||
import { DSpaceRESTv2Service } from "../../dspace-rest-v2/dspace-rest-v2.service";
|
||||
import { CacheService } from "../cache/cache.service";
|
||||
import { GlobalConfig } from "../../../../config";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class CollectionDataEffects {
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private restApi: DSpaceRESTv2Service,
|
||||
private cache: CacheService
|
||||
) {}
|
||||
|
||||
// TODO, results of a findall aren't retrieved from cache for now,
|
||||
// because currently the cache is more of an object store. We need to move
|
||||
// more towards memoization for things like this.
|
||||
@Effect() findAll$ = this.actions$
|
||||
.ofType(CollectionFindMultipleActionTypes.FIND_MULTI_REQUEST)
|
||||
.switchMap(() => {
|
||||
return this.restApi.get('/collections')
|
||||
.map((data: DSpaceRESTV2Response) => new DSpaceRESTv2Serializer(Collection).deserializeArray(data))
|
||||
.do((collections: Collection[]) => {
|
||||
collections.forEach((collection) => {
|
||||
this.cache.add(collection, GlobalConfig.cache.msToLive);
|
||||
});
|
||||
})
|
||||
.map((collections: Array<Collection>) => collections.map(collection => collection.uuid))
|
||||
.map((uuids: Array<string>) => new CollectionFindMultipleSuccessAction(uuids))
|
||||
.catch((errorMsg: string) => Observable.of(new CollectionFindMultipleErrorAction(errorMsg)));
|
||||
});
|
||||
|
||||
@Effect() findById$ = this.actions$
|
||||
.ofType(CollectionFindSingleActionTypes.FIND_BY_ID_REQUEST)
|
||||
.switchMap(action => {
|
||||
return this.restApi.get(`/collections/${action.payload}`)
|
||||
.map((data: DSpaceRESTV2Response) => new DSpaceRESTv2Serializer(Collection).deserialize(data))
|
||||
.do((collection: Collection) => {
|
||||
this.cache.add(collection, GlobalConfig.cache.msToLive);
|
||||
})
|
||||
.map((collection: Collection) => new CollectionFindByIdSuccessAction(collection.uuid))
|
||||
.catch((errorMsg: string) => Observable.of(new CollectionFindByIdErrorAction(errorMsg)));
|
||||
});
|
||||
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { CollectionFindMultipleState, findMultipleReducer } from "./collection-find-multiple.reducer";
|
||||
import { CollectionFindSingleState, findSingleReducer } from "./collection-find-single.reducer";
|
||||
|
||||
export interface CollectionDataState {
|
||||
findMultiple: CollectionFindMultipleState,
|
||||
findSingle: CollectionFindSingleState
|
||||
}
|
||||
|
||||
const reducers = {
|
||||
findMultiple: findMultipleReducer,
|
||||
findSingle: findSingleReducer
|
||||
};
|
||||
|
||||
export function collectionDataReducer(state: any, action: any) {
|
||||
return combineReducers(reducers)(state, action);
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { CollectionDataState } from "./collection-data.reducer";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { Collection } from "../../shared/collection.model";
|
||||
import { CollectionFindMultipleRequestAction } from "./collection-find-multiple.actions";
|
||||
import { CollectionFindByIdRequestAction } from "./collection-find-single.actions";
|
||||
import { CacheService } from "../cache/cache.service";
|
||||
import 'rxjs/add/observable/forkJoin';
|
||||
|
||||
@Injectable()
|
||||
export class CollectionDataService {
|
||||
constructor(
|
||||
private store: Store<CollectionDataState>,
|
||||
private cache: CacheService
|
||||
) { }
|
||||
|
||||
findAll(scopeID?: string): Observable<Collection[]> {
|
||||
this.store.dispatch(new CollectionFindMultipleRequestAction(scopeID));
|
||||
//get an observable of the IDs from the collectionData store
|
||||
return this.store.select<Array<string>>('core', 'collectionData', 'findMultiple', 'collectionUUIDs')
|
||||
.flatMap((collectionUUIDs: Array<string>) => {
|
||||
// use those IDs to fetch the actual collection objects from the cache
|
||||
return this.cache.getList<Collection>(collectionUUIDs);
|
||||
});
|
||||
}
|
||||
|
||||
findById(id: string): Observable<Collection> {
|
||||
this.store.dispatch(new CollectionFindByIdRequestAction(id));
|
||||
return this.store.select<string>('core', 'collectionData', 'findSingle', 'collectionUUID')
|
||||
.flatMap((collectionUUID: string) => {
|
||||
return this.cache.get<Collection>(collectionUUID);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../../shared/ngrx/type";
|
||||
import { PaginationOptions } from "../../shared/pagination-options.model";
|
||||
import { SortOptions } from "../../shared/sort-options.model";
|
||||
|
||||
export const CollectionFindMultipleActionTypes = {
|
||||
FIND_MULTI_REQUEST: type('dspace/core/data/collection/FIND_MULTI_REQUEST'),
|
||||
FIND_MULTI_SUCCESS: type('dspace/core/data/collection/FIND_MULTI_SUCCESS'),
|
||||
FIND_MULTI_ERROR: type('dspace/core/data/collection/FIND_MULTI_ERROR')
|
||||
};
|
||||
|
||||
export class CollectionFindMultipleRequestAction implements Action {
|
||||
type = CollectionFindMultipleActionTypes.FIND_MULTI_REQUEST;
|
||||
payload: {
|
||||
scopeID: string,
|
||||
paginationOptions: PaginationOptions,
|
||||
sortOptions: SortOptions
|
||||
};
|
||||
|
||||
constructor(
|
||||
scopeID?: string,
|
||||
paginationOptions: PaginationOptions = new PaginationOptions(),
|
||||
sortOptions: SortOptions = new SortOptions()
|
||||
) {
|
||||
this.payload = {
|
||||
scopeID,
|
||||
paginationOptions,
|
||||
sortOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionFindMultipleSuccessAction implements Action {
|
||||
type = CollectionFindMultipleActionTypes.FIND_MULTI_SUCCESS;
|
||||
payload: Array<string>;
|
||||
|
||||
constructor(collectionUUIDs: Array<string>) {
|
||||
this.payload = collectionUUIDs;
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionFindMultipleErrorAction implements Action {
|
||||
type = CollectionFindMultipleActionTypes.FIND_MULTI_ERROR;
|
||||
payload: string;
|
||||
|
||||
constructor(errorMessage: string) {
|
||||
this.payload = errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
export type CollectionFindMultipleAction
|
||||
= CollectionFindMultipleRequestAction
|
||||
| CollectionFindMultipleSuccessAction
|
||||
| CollectionFindMultipleErrorAction;
|
@@ -1,59 +0,0 @@
|
||||
import { PaginationOptions } from "../../shared/pagination-options.model";
|
||||
import { SortOptions } from "../../shared/sort-options.model";
|
||||
import {
|
||||
CollectionFindMultipleAction,
|
||||
CollectionFindMultipleActionTypes
|
||||
} from "./collection-find-multiple.actions";
|
||||
|
||||
export interface CollectionFindMultipleState {
|
||||
scopeID: string;
|
||||
collectionUUIDs: Array<String>;
|
||||
isLoading: boolean;
|
||||
errorMessage: string;
|
||||
paginationOptions: PaginationOptions;
|
||||
sortOptions: SortOptions;
|
||||
}
|
||||
|
||||
const initialState: CollectionFindMultipleState = {
|
||||
scopeID: undefined,
|
||||
collectionUUIDs: [],
|
||||
isLoading: false,
|
||||
errorMessage: undefined,
|
||||
paginationOptions: undefined,
|
||||
sortOptions: undefined
|
||||
};
|
||||
|
||||
export const findMultipleReducer = (state = initialState, action: CollectionFindMultipleAction): CollectionFindMultipleState => {
|
||||
switch (action.type) {
|
||||
|
||||
case CollectionFindMultipleActionTypes.FIND_MULTI_REQUEST: {
|
||||
return Object.assign({}, state, {
|
||||
scopeID: action.payload.scopeID,
|
||||
collectionUUIDs: [],
|
||||
isLoading: true,
|
||||
errorMessage: undefined,
|
||||
paginationOptions: action.payload.paginationOptions,
|
||||
sortOptions: action.payload.sortOptions
|
||||
});
|
||||
}
|
||||
|
||||
case CollectionFindMultipleActionTypes.FIND_MULTI_SUCCESS: {
|
||||
return Object.assign({}, state, {
|
||||
isLoading: false,
|
||||
collectionUUIDs: action.payload,
|
||||
errorMessage: undefined
|
||||
});
|
||||
}
|
||||
|
||||
case CollectionFindMultipleActionTypes.FIND_MULTI_ERROR: {
|
||||
return Object.assign({}, state, {
|
||||
isLoading: false,
|
||||
errorMessage: action.payload
|
||||
});
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
@@ -1,42 +0,0 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../../shared/ngrx/type";
|
||||
import { Collection } from "../../shared/collection.model";
|
||||
|
||||
export const CollectionFindSingleActionTypes = {
|
||||
FIND_BY_ID_REQUEST: type('dspace/core/data/collection/FIND_BY_ID_REQUEST'),
|
||||
FIND_BY_ID_SUCCESS: type('dspace/core/data/collection/FIND_BY_ID_SUCCESS'),
|
||||
FIND_BY_ID_ERROR: type('dspace/core/data/collection/FIND_BY_ID_ERROR')
|
||||
};
|
||||
|
||||
export class CollectionFindByIdRequestAction implements Action {
|
||||
type = CollectionFindSingleActionTypes.FIND_BY_ID_REQUEST;
|
||||
payload: string;
|
||||
|
||||
constructor(requestID: string) {
|
||||
this.payload = requestID;
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionFindByIdSuccessAction implements Action {
|
||||
type = CollectionFindSingleActionTypes.FIND_BY_ID_SUCCESS;
|
||||
payload: string;
|
||||
|
||||
constructor(collectionUUID: string) {
|
||||
this.payload = collectionUUID;
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionFindByIdErrorAction implements Action {
|
||||
type = CollectionFindSingleActionTypes.FIND_BY_ID_ERROR;
|
||||
payload: string;
|
||||
|
||||
constructor(errorMessage: string) {
|
||||
this.payload = errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
export type CollectionFindSingleAction
|
||||
= CollectionFindByIdRequestAction
|
||||
| CollectionFindByIdSuccessAction
|
||||
| CollectionFindByIdErrorAction;
|
||||
|
@@ -1,51 +0,0 @@
|
||||
import { Collection } from "../../shared/collection.model";
|
||||
import {
|
||||
CollectionFindSingleAction,
|
||||
CollectionFindSingleActionTypes
|
||||
} from "./collection-find-single.actions";
|
||||
|
||||
export interface CollectionFindSingleState {
|
||||
isLoading: boolean;
|
||||
errorMessage: string;
|
||||
requestedID: string;
|
||||
collectionUUID: string;
|
||||
}
|
||||
|
||||
const initialState: CollectionFindSingleState = {
|
||||
isLoading: false,
|
||||
errorMessage: undefined,
|
||||
requestedID: undefined,
|
||||
collectionUUID: undefined
|
||||
};
|
||||
|
||||
export const findSingleReducer = (state = initialState, action: CollectionFindSingleAction): CollectionFindSingleState => {
|
||||
switch (action.type) {
|
||||
|
||||
case CollectionFindSingleActionTypes.FIND_BY_ID_REQUEST: {
|
||||
return Object.assign({}, state, {
|
||||
isLoading: true,
|
||||
errorMessage: undefined,
|
||||
requestedID: action.payload
|
||||
});
|
||||
}
|
||||
|
||||
case CollectionFindSingleActionTypes.FIND_BY_ID_SUCCESS: {
|
||||
return Object.assign({}, state, {
|
||||
isLoading: false,
|
||||
errorMessage: undefined,
|
||||
collectionUUID: action.payload
|
||||
});
|
||||
}
|
||||
|
||||
case CollectionFindSingleActionTypes.FIND_BY_ID_ERROR: {
|
||||
return Object.assign({}, state, {
|
||||
isLoading: false,
|
||||
errorMessage: action.payload
|
||||
});
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
96
src/app/core/data-services/data.actions.ts
Normal file
96
src/app/core/data-services/data.actions.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { OpaqueToken } from "@angular/core";
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../shared/ngrx/type";
|
||||
import { PaginationOptions } from "../shared/pagination-options.model";
|
||||
import { SortOptions } from "../shared/sort-options.model";
|
||||
|
||||
export const DataActionTypes = {
|
||||
FIND_BY_ID_REQUEST: type('dspace/core/data/FIND_BY_ID_REQUEST'),
|
||||
FIND_ALL_REQUEST: type('dspace/core/data/FIND_ALL_REQUEST'),
|
||||
SUCCESS: type('dspace/core/data/SUCCESS'),
|
||||
ERROR: type('dspace/core/data/ERROR')
|
||||
};
|
||||
|
||||
export class DataFindAllRequestAction implements Action {
|
||||
type = DataActionTypes.FIND_ALL_REQUEST;
|
||||
payload: {
|
||||
key: string,
|
||||
service: OpaqueToken,
|
||||
scopeID: string,
|
||||
paginationOptions: PaginationOptions,
|
||||
sortOptions: SortOptions
|
||||
};
|
||||
|
||||
constructor(
|
||||
key: string,
|
||||
service: OpaqueToken,
|
||||
scopeID?: string,
|
||||
paginationOptions: PaginationOptions = new PaginationOptions(),
|
||||
sortOptions: SortOptions = new SortOptions()
|
||||
) {
|
||||
this.payload = {
|
||||
key,
|
||||
service,
|
||||
scopeID,
|
||||
paginationOptions,
|
||||
sortOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DataFindByIDRequestAction implements Action {
|
||||
type = DataActionTypes.FIND_BY_ID_REQUEST;
|
||||
payload: {
|
||||
key: string,
|
||||
service: OpaqueToken,
|
||||
resourceID: string
|
||||
};
|
||||
|
||||
constructor(
|
||||
key: string,
|
||||
service: OpaqueToken,
|
||||
resourceID: string
|
||||
) {
|
||||
this.payload = {
|
||||
key,
|
||||
service,
|
||||
resourceID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DataSuccessAction implements Action {
|
||||
type = DataActionTypes.SUCCESS;
|
||||
payload: {
|
||||
key: string,
|
||||
resourceUUIDs: Array<string>
|
||||
};
|
||||
|
||||
constructor(key: string, resourceUUIDs: Array<string>) {
|
||||
this.payload = {
|
||||
key,
|
||||
resourceUUIDs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class DataErrorAction implements Action {
|
||||
type = DataActionTypes.ERROR;
|
||||
payload: {
|
||||
key: string,
|
||||
errorMessage: string
|
||||
};
|
||||
|
||||
constructor(key: string, errorMessage: string) {
|
||||
this.payload = {
|
||||
key,
|
||||
errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type DataAction
|
||||
= DataFindAllRequestAction
|
||||
| DataFindByIDRequestAction
|
||||
| DataSuccessAction
|
||||
| DataErrorAction;
|
60
src/app/core/data-services/data.effects.ts
Normal file
60
src/app/core/data-services/data.effects.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { Observable } from "rxjs";
|
||||
import { DSpaceRESTV2Response } from "../dspace-rest-v2/dspace-rest-v2-response.model";
|
||||
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
||||
import { CacheService } from "./cache/cache.service";
|
||||
import { GlobalConfig } from "../../../config";
|
||||
import { CacheableObject } from "./cache/cache.reducer";
|
||||
import { Serializer } from "../serializer";
|
||||
import {
|
||||
DataActionTypes, DataFindAllRequestAction, DataSuccessAction,
|
||||
DataErrorAction, DataFindByIDRequestAction, DataAction
|
||||
} from "./data.actions";
|
||||
import { DataService } from "./data.service";
|
||||
|
||||
export abstract class DataEffects<T extends CacheableObject> {
|
||||
protected abstract getFindAllEndpoint(action: DataFindAllRequestAction): string;
|
||||
protected abstract getFindByIdEndpoint(action: DataFindByIDRequestAction): string;
|
||||
protected abstract getSerializer(): Serializer<T>;
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private restApi: DSpaceRESTv2Service,
|
||||
private cache: CacheService,
|
||||
private dataService: DataService<T>
|
||||
) {}
|
||||
|
||||
// TODO, results of a findall aren't retrieved from cache for now,
|
||||
// because currently the cache is more of an object store. We need to move
|
||||
// more towards memoization for things like this.
|
||||
protected findAll = this.actions$
|
||||
.ofType(DataActionTypes.FIND_ALL_REQUEST)
|
||||
.filter((action: DataFindAllRequestAction) => action.payload.service === this.dataService.name)
|
||||
.switchMap((action: DataFindAllRequestAction) => {
|
||||
//TODO scope, pagination, sorting -> when we know how that works in rest
|
||||
return this.restApi.get(this.getFindAllEndpoint(action))
|
||||
.map((data: DSpaceRESTV2Response) => this.getSerializer().deserializeArray(data))
|
||||
.do((ts: T[]) => {
|
||||
ts.forEach((t) => {
|
||||
this.cache.add(t, GlobalConfig.cache.msToLive);
|
||||
});
|
||||
})
|
||||
.map((ts: Array<T>) => ts.map(t => t.uuid))
|
||||
.map((ids: Array<string>) => new DataSuccessAction(action.payload.key, ids))
|
||||
.catch((errorMsg: string) => Observable.of(new DataErrorAction(action.payload.key, errorMsg)));
|
||||
});
|
||||
|
||||
protected findById = this.actions$
|
||||
.ofType(DataActionTypes.FIND_BY_ID_REQUEST)
|
||||
.filter((action: DataFindAllRequestAction) => action.payload.service === this.dataService.name)
|
||||
.switchMap((action: DataFindByIDRequestAction) => {
|
||||
return this.restApi.get(this.getFindByIdEndpoint(action))
|
||||
.map((data: DSpaceRESTV2Response) => this.getSerializer().deserialize(data))
|
||||
.do((t: T) => {
|
||||
this.cache.add(t, GlobalConfig.cache.msToLive);
|
||||
})
|
||||
.map((t: T) => new DataSuccessAction(action.payload.key, [t.uuid]))
|
||||
.catch((errorMsg: string) => Observable.of(new DataErrorAction(action.payload.key, errorMsg)));
|
||||
});
|
||||
|
||||
}
|
100
src/app/core/data-services/data.reducer.ts
Normal file
100
src/app/core/data-services/data.reducer.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { PaginationOptions } from "../shared/pagination-options.model";
|
||||
import { SortOptions } from "../shared/sort-options.model";
|
||||
import {
|
||||
DataAction, DataActionTypes, DataFindAllRequestAction,
|
||||
DataSuccessAction, DataErrorAction, DataFindByIDRequestAction
|
||||
} from "./data.actions";
|
||||
import { OpaqueToken } from "@angular/core";
|
||||
|
||||
export interface DataRequestState {
|
||||
service: OpaqueToken
|
||||
scopeID: string;
|
||||
resourceID: string;
|
||||
resourceUUIDs: Array<String>;
|
||||
resourceType: String;
|
||||
isLoading: boolean;
|
||||
errorMessage: string;
|
||||
paginationOptions: PaginationOptions;
|
||||
sortOptions: SortOptions;
|
||||
timeAdded: number;
|
||||
msToLive: number;
|
||||
}
|
||||
|
||||
export interface DataState {
|
||||
[key: string]: DataRequestState
|
||||
}
|
||||
|
||||
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
||||
const initialState = Object.create(null);
|
||||
|
||||
export const dataReducer = (state = initialState, action: DataAction): DataState => {
|
||||
switch (action.type) {
|
||||
|
||||
case DataActionTypes.FIND_ALL_REQUEST: {
|
||||
return findAllRequest(state, <DataFindAllRequestAction> action);
|
||||
}
|
||||
|
||||
case DataActionTypes.FIND_BY_ID_REQUEST: {
|
||||
return findByIDRequest(state, <DataFindByIDRequestAction> action);
|
||||
}
|
||||
|
||||
case DataActionTypes.SUCCESS: {
|
||||
return success(state, <DataSuccessAction> action);
|
||||
}
|
||||
|
||||
case DataActionTypes.ERROR: {
|
||||
return error(state, <DataErrorAction> action);
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function findAllRequest(state: DataState, action: DataFindAllRequestAction): DataState {
|
||||
return Object.assign({}, state, {
|
||||
[action.payload.key]: {
|
||||
service: action.payload.service,
|
||||
scopeID: action.payload.scopeID,
|
||||
resourceUUIDs: [],
|
||||
isLoading: true,
|
||||
errorMessage: undefined,
|
||||
paginationOptions: action.payload.paginationOptions,
|
||||
sortOptions: action.payload.sortOptions
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findByIDRequest(state: DataState, action: DataFindByIDRequestAction): DataState {
|
||||
return Object.assign({}, state, {
|
||||
[action.payload.key]: {
|
||||
service: action.payload.service,
|
||||
resourceID: action.payload.resourceID,
|
||||
resourceUUIDs: [],
|
||||
isLoading: true,
|
||||
errorMessage: undefined,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function success(state: DataState, action: DataSuccessAction): DataState {
|
||||
return Object.assign({}, state, {
|
||||
[action.payload.key]: Object.assign({}, state[action.payload.key], {
|
||||
isLoading: false,
|
||||
resourceUUIDs: action.payload.resourceUUIDs,
|
||||
errorMessage: undefined
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function error(state: DataState, action: DataErrorAction): DataState {
|
||||
return Object.assign({}, state, {
|
||||
[action.payload.key]: Object.assign({}, state[action.payload.key], {
|
||||
isLoading: false,
|
||||
errorMessage: action.payload.errorMessage
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
44
src/app/core/data-services/data.service.ts
Normal file
44
src/app/core/data-services/data.service.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { OpaqueToken } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { CacheService } from "./cache/cache.service";
|
||||
import { CacheableObject } from "./cache/cache.reducer";
|
||||
import { DataState } from "./data.reducer";
|
||||
import { DataFindAllRequestAction, DataFindByIDRequestAction } from "./data.actions";
|
||||
import { ParamHash } from "../shared/param-hash";
|
||||
import { isNotEmpty } from "../../shared/empty.util";
|
||||
|
||||
export abstract class DataService<T extends CacheableObject> {
|
||||
abstract name: OpaqueToken;
|
||||
|
||||
constructor(
|
||||
private store: Store<DataState>,
|
||||
private cache: CacheService
|
||||
) { }
|
||||
|
||||
findAll(scopeID?: string): Observable<Array<T>> {
|
||||
const key = new ParamHash(this.name, 'findAll', scopeID).toString();
|
||||
this.store.dispatch(new DataFindAllRequestAction(key, this.name, scopeID));
|
||||
//get an observable of the IDs from the store
|
||||
return this.store.select<Array<string>>('core', 'data', key, 'resourceUUIDs')
|
||||
.flatMap((resourceUUIDs: Array<string>) => {
|
||||
// use those IDs to fetch the actual objects from the cache
|
||||
return this.cache.getList<T>(resourceUUIDs);
|
||||
});
|
||||
}
|
||||
|
||||
findById(id: string): Observable<T> {
|
||||
const key = new ParamHash(this.name, 'findById', id).toString();
|
||||
this.store.dispatch(new DataFindByIDRequestAction(key, this.name, id));
|
||||
return this.store.select<Array<string>>('core', 'data', key, 'resourceUUIDs')
|
||||
.flatMap((resourceUUIDs: Array<string>) => {
|
||||
if(isNotEmpty(resourceUUIDs)) {
|
||||
return this.cache.get<T>(resourceUUIDs[0]);
|
||||
}
|
||||
else {
|
||||
return Observable.of(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
38
src/app/core/data-services/item-data.effects.ts
Normal file
38
src/app/core/data-services/item-data.effects.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { DataEffects } from "./data.effects";
|
||||
import { Serializer } from "../serializer";
|
||||
import { Item } from "../shared/item.model";
|
||||
import { DSpaceRESTv2Serializer } from "../dspace-rest-v2/dspace-rest-v2.serializer";
|
||||
import { CacheService } from "./cache/cache.service";
|
||||
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { DataFindAllRequestAction, DataFindByIDRequestAction } from "./data.actions";
|
||||
import { ItemDataService } from "./item-data.service";
|
||||
|
||||
@Injectable()
|
||||
export class ItemDataEffects extends DataEffects<Item> {
|
||||
constructor(
|
||||
actions$: Actions,
|
||||
restApi: DSpaceRESTv2Service,
|
||||
cache: CacheService,
|
||||
dataService: ItemDataService
|
||||
) {
|
||||
super(actions$, restApi, cache, dataService);
|
||||
}
|
||||
|
||||
protected getFindAllEndpoint(action: DataFindAllRequestAction): string {
|
||||
return '/items';
|
||||
}
|
||||
|
||||
protected getFindByIdEndpoint(action: DataFindByIDRequestAction): string {
|
||||
return `/items/${action.payload.resourceID}`;
|
||||
}
|
||||
|
||||
protected getSerializer(): Serializer<Item> {
|
||||
return new DSpaceRESTv2Serializer(Item);
|
||||
}
|
||||
|
||||
@Effect() findAll$ = this.findAll;
|
||||
|
||||
@Effect() findById$ = this.findById;
|
||||
}
|
19
src/app/core/data-services/item-data.service.ts
Normal file
19
src/app/core/data-services/item-data.service.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable, OpaqueToken } from "@angular/core";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { DataService } from "./data.service";
|
||||
import { Item } from "../shared/item.model";
|
||||
import { CacheService } from "./cache/cache.service";
|
||||
import { DataState } from "./data.reducer";
|
||||
|
||||
@Injectable()
|
||||
export class ItemDataService extends DataService<Item> {
|
||||
name = new OpaqueToken('ItemDataService');
|
||||
|
||||
constructor(
|
||||
store: Store<DataState>,
|
||||
cache: CacheService
|
||||
) {
|
||||
super(store, cache);
|
||||
}
|
||||
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { Item } from "../../shared/item.model";
|
||||
import { Observable } from "rxjs";
|
||||
import {
|
||||
ItemFindMultipleActionTypes,
|
||||
ItemFindMultipleSuccessAction,
|
||||
ItemFindMultipleErrorAction
|
||||
} from "./item-find-multiple.actions";
|
||||
import {
|
||||
ItemFindSingleActionTypes,
|
||||
ItemFindByIdSuccessAction,
|
||||
ItemFindByIdErrorAction
|
||||
} from "./item-find-single.actions";
|
||||
import { DSpaceRESTV2Response } from "../../dspace-rest-v2/dspace-rest-v2-response.model";
|
||||
import { DSpaceRESTv2Serializer } from "../../dspace-rest-v2/dspace-rest-v2.serializer";
|
||||
import { DSpaceRESTv2Service } from "../../dspace-rest-v2/dspace-rest-v2.service";
|
||||
import { CacheService } from "../cache/cache.service";
|
||||
import { GlobalConfig } from "../../../../config";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class ItemDataEffects {
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private restApi: DSpaceRESTv2Service,
|
||||
private cache: CacheService
|
||||
) {}
|
||||
|
||||
// TODO, results of a findall aren't retrieved from cache for now,
|
||||
// because currently the cache is more of an object store. We need to move
|
||||
// more towards memoization for things like this.
|
||||
@Effect() findAll$ = this.actions$
|
||||
.ofType(ItemFindMultipleActionTypes.FIND_MULTI_REQUEST)
|
||||
.switchMap(() => {
|
||||
return this.restApi.get('/items')
|
||||
.map((data: DSpaceRESTV2Response) => new DSpaceRESTv2Serializer(Item).deserializeArray(data))
|
||||
.do((items: Item[]) => {
|
||||
items.forEach((item) => {
|
||||
this.cache.add(item, GlobalConfig.cache.msToLive);
|
||||
});
|
||||
})
|
||||
.map((items: Array<Item>) => items.map(item => item.uuid))
|
||||
.map((uuids: Array<string>) => new ItemFindMultipleSuccessAction(uuids))
|
||||
.catch((errorMsg: string) => Observable.of(new ItemFindMultipleErrorAction(errorMsg)));
|
||||
});
|
||||
|
||||
@Effect() findById$ = this.actions$
|
||||
.ofType(ItemFindSingleActionTypes.FIND_BY_ID_REQUEST)
|
||||
.switchMap(action => {
|
||||
return this.restApi.get(`/items/${action.payload}`)
|
||||
.map((data: DSpaceRESTV2Response) => new DSpaceRESTv2Serializer(Item).deserialize(data))
|
||||
.do((item: Item) => {
|
||||
this.cache.add(item, GlobalConfig.cache.msToLive);
|
||||
})
|
||||
.map((item: Item) => new ItemFindByIdSuccessAction(item.uuid))
|
||||
.catch((errorMsg: string) => Observable.of(new ItemFindByIdErrorAction(errorMsg)));
|
||||
});
|
||||
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { ItemFindMultipleState, findMultipleReducer } from "./item-find-multiple.reducer";
|
||||
import { ItemFindSingleState, findSingleReducer } from "./item-find-single.reducer";
|
||||
|
||||
export interface ItemDataState {
|
||||
findMultiple: ItemFindMultipleState,
|
||||
findSingle: ItemFindSingleState
|
||||
}
|
||||
|
||||
const reducers = {
|
||||
findMultiple: findMultipleReducer,
|
||||
findSingle: findSingleReducer
|
||||
};
|
||||
|
||||
export function itemDataReducer(state: any, action: any) {
|
||||
return combineReducers(reducers)(state, action);
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { ItemDataState } from "./item-data.reducer";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { Item } from "../../shared/item.model";
|
||||
import { ItemFindMultipleRequestAction } from "./item-find-multiple.actions";
|
||||
import { ItemFindByIdRequestAction } from "./item-find-single.actions";
|
||||
import { CacheService } from "../cache/cache.service";
|
||||
import 'rxjs/add/observable/forkJoin';
|
||||
|
||||
@Injectable()
|
||||
export class ItemDataService {
|
||||
constructor(
|
||||
private store: Store<ItemDataState>,
|
||||
private cache: CacheService
|
||||
) { }
|
||||
|
||||
findAll(scopeID?: string): Observable<Item[]> {
|
||||
this.store.dispatch(new ItemFindMultipleRequestAction(scopeID));
|
||||
//get an observable of the IDs from the itemData store
|
||||
return this.store.select<Array<string>>('core', 'itemData', 'findMultiple', 'itemUUIDs')
|
||||
.flatMap((itemUUIDs: Array<string>) => {
|
||||
// use those IDs to fetch the actual item objects from the cache
|
||||
return this.cache.getList<Item>(itemUUIDs);
|
||||
});
|
||||
}
|
||||
|
||||
findById(id: string): Observable<Item> {
|
||||
this.store.dispatch(new ItemFindByIdRequestAction(id));
|
||||
return this.store.select<string>('core', 'itemData', 'findSingle', 'itemUUID')
|
||||
.flatMap((itemUUID: string) => {
|
||||
return this.cache.get<Item>(itemUUID);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../../shared/ngrx/type";
|
||||
import { PaginationOptions } from "../../shared/pagination-options.model";
|
||||
import { SortOptions } from "../../shared/sort-options.model";
|
||||
|
||||
export const ItemFindMultipleActionTypes = {
|
||||
FIND_MULTI_REQUEST: type('dspace/core/data/item/FIND_MULTI_REQUEST'),
|
||||
FIND_MULTI_SUCCESS: type('dspace/core/data/item/FIND_MULTI_SUCCESS'),
|
||||
FIND_MULTI_ERROR: type('dspace/core/data/item/FIND_MULTI_ERROR')
|
||||
};
|
||||
|
||||
export class ItemFindMultipleRequestAction implements Action {
|
||||
type = ItemFindMultipleActionTypes.FIND_MULTI_REQUEST;
|
||||
payload: {
|
||||
scopeID: string,
|
||||
paginationOptions: PaginationOptions,
|
||||
sortOptions: SortOptions
|
||||
};
|
||||
|
||||
constructor(
|
||||
scopeID?: string,
|
||||
paginationOptions: PaginationOptions = new PaginationOptions(),
|
||||
sortOptions: SortOptions = new SortOptions()
|
||||
) {
|
||||
this.payload = {
|
||||
scopeID,
|
||||
paginationOptions,
|
||||
sortOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ItemFindMultipleSuccessAction implements Action {
|
||||
type = ItemFindMultipleActionTypes.FIND_MULTI_SUCCESS;
|
||||
payload: Array<string>;
|
||||
|
||||
constructor(itemUUIDs: Array<string>) {
|
||||
this.payload = itemUUIDs;
|
||||
}
|
||||
}
|
||||
|
||||
export class ItemFindMultipleErrorAction implements Action {
|
||||
type = ItemFindMultipleActionTypes.FIND_MULTI_ERROR;
|
||||
payload: string;
|
||||
|
||||
constructor(errorMessage: string) {
|
||||
this.payload = errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
export type ItemFindMultipleAction
|
||||
= ItemFindMultipleRequestAction
|
||||
| ItemFindMultipleSuccessAction
|
||||
| ItemFindMultipleErrorAction;
|
@@ -1,59 +0,0 @@
|
||||
import { PaginationOptions } from "../../shared/pagination-options.model";
|
||||
import { SortOptions } from "../../shared/sort-options.model";
|
||||
import {
|
||||
ItemFindMultipleAction,
|
||||
ItemFindMultipleActionTypes
|
||||
} from "./item-find-multiple.actions";
|
||||
|
||||
export interface ItemFindMultipleState {
|
||||
scopeID: string;
|
||||
itemUUIDs: Array<String>;
|
||||
isLoading: boolean;
|
||||
errorMessage: string;
|
||||
paginationOptions: PaginationOptions;
|
||||
sortOptions: SortOptions;
|
||||
}
|
||||
|
||||
const initialState: ItemFindMultipleState = {
|
||||
scopeID: undefined,
|
||||
itemUUIDs: [],
|
||||
isLoading: false,
|
||||
errorMessage: undefined,
|
||||
paginationOptions: undefined,
|
||||
sortOptions: undefined
|
||||
};
|
||||
|
||||
export const findMultipleReducer = (state = initialState, action: ItemFindMultipleAction): ItemFindMultipleState => {
|
||||
switch (action.type) {
|
||||
|
||||
case ItemFindMultipleActionTypes.FIND_MULTI_REQUEST: {
|
||||
return Object.assign({}, state, {
|
||||
scopeID: action.payload.scopeID,
|
||||
itemUUIDs: [],
|
||||
isLoading: true,
|
||||
errorMessage: undefined,
|
||||
paginationOptions: action.payload.paginationOptions,
|
||||
sortOptions: action.payload.sortOptions
|
||||
});
|
||||
}
|
||||
|
||||
case ItemFindMultipleActionTypes.FIND_MULTI_SUCCESS: {
|
||||
return Object.assign({}, state, {
|
||||
isLoading: false,
|
||||
itemUUIDs: action.payload,
|
||||
errorMessage: undefined
|
||||
});
|
||||
}
|
||||
|
||||
case ItemFindMultipleActionTypes.FIND_MULTI_ERROR: {
|
||||
return Object.assign({}, state, {
|
||||
isLoading: false,
|
||||
errorMessage: action.payload
|
||||
});
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
@@ -1,42 +0,0 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../../shared/ngrx/type";
|
||||
import { Item } from "../../shared/item.model";
|
||||
|
||||
export const ItemFindSingleActionTypes = {
|
||||
FIND_BY_ID_REQUEST: type('dspace/core/data/item/FIND_BY_ID_REQUEST'),
|
||||
FIND_BY_ID_SUCCESS: type('dspace/core/data/item/FIND_BY_ID_SUCCESS'),
|
||||
FIND_BY_ID_ERROR: type('dspace/core/data/item/FIND_BY_ID_ERROR')
|
||||
};
|
||||
|
||||
export class ItemFindByIdRequestAction implements Action {
|
||||
type = ItemFindSingleActionTypes.FIND_BY_ID_REQUEST;
|
||||
payload: string;
|
||||
|
||||
constructor(requestID: string) {
|
||||
this.payload = requestID;
|
||||
}
|
||||
}
|
||||
|
||||
export class ItemFindByIdSuccessAction implements Action {
|
||||
type = ItemFindSingleActionTypes.FIND_BY_ID_SUCCESS;
|
||||
payload: string;
|
||||
|
||||
constructor(itemUUID: string) {
|
||||
this.payload = itemUUID;
|
||||
}
|
||||
}
|
||||
|
||||
export class ItemFindByIdErrorAction implements Action {
|
||||
type = ItemFindSingleActionTypes.FIND_BY_ID_ERROR;
|
||||
payload: string;
|
||||
|
||||
constructor(errorMessage: string) {
|
||||
this.payload = errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
export type ItemFindSingleAction
|
||||
= ItemFindByIdRequestAction
|
||||
| ItemFindByIdSuccessAction
|
||||
| ItemFindByIdErrorAction;
|
||||
|
@@ -1,51 +0,0 @@
|
||||
import { Item } from "../../shared/item.model";
|
||||
import {
|
||||
ItemFindSingleAction,
|
||||
ItemFindSingleActionTypes
|
||||
} from "./item-find-single.actions";
|
||||
|
||||
export interface ItemFindSingleState {
|
||||
isLoading: boolean;
|
||||
errorMessage: string;
|
||||
requestedID: string;
|
||||
itemUUID: string;
|
||||
}
|
||||
|
||||
const initialState: ItemFindSingleState = {
|
||||
isLoading: false,
|
||||
errorMessage: undefined,
|
||||
requestedID: undefined,
|
||||
itemUUID: undefined
|
||||
};
|
||||
|
||||
export const findSingleReducer = (state = initialState, action: ItemFindSingleAction): ItemFindSingleState => {
|
||||
switch (action.type) {
|
||||
|
||||
case ItemFindSingleActionTypes.FIND_BY_ID_REQUEST: {
|
||||
return Object.assign({}, state, {
|
||||
isLoading: true,
|
||||
errorMessage: undefined,
|
||||
requestedID: action.payload
|
||||
});
|
||||
}
|
||||
|
||||
case ItemFindSingleActionTypes.FIND_BY_ID_SUCCESS: {
|
||||
return Object.assign({}, state, {
|
||||
isLoading: false,
|
||||
errorMessage: undefined,
|
||||
itemUUID: action.payload
|
||||
});
|
||||
}
|
||||
|
||||
case ItemFindSingleActionTypes.FIND_BY_ID_ERROR: {
|
||||
return Object.assign({}, state, {
|
||||
isLoading: false,
|
||||
errorMessage: action.payload
|
||||
});
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
58
src/app/core/shared/param-hash.spec.ts
Normal file
58
src/app/core/shared/param-hash.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { ParamHash } from "./param-hash";
|
||||
describe("ParamHash", () => {
|
||||
|
||||
it("should return a hash for a set of parameters", () => {
|
||||
const hash = new ParamHash('azerty', true, 23).toString();
|
||||
|
||||
expect(hash).not.toBeNull();
|
||||
expect(hash).not.toBe('');
|
||||
});
|
||||
|
||||
it("should work with both simple and complex objects as parameters", () => {
|
||||
const hash = new ParamHash('azerty', true, 23, { "a": { "b": ['azerty', true] }, "c": 23 }).toString();
|
||||
|
||||
expect(hash).not.toBeNull();
|
||||
expect(hash).not.toBe('');
|
||||
});
|
||||
|
||||
it("should work with null or undefined as parameters", () => {
|
||||
const hash1 = new ParamHash(undefined).toString();
|
||||
const hash2 = new ParamHash(null).toString();
|
||||
const hash3 = new ParamHash(undefined, null).toString();
|
||||
|
||||
expect(hash1).not.toBeNull();
|
||||
expect(hash1).not.toBe('');
|
||||
expect(hash2).not.toBeNull();
|
||||
expect(hash2).not.toBe('');
|
||||
expect(hash3).not.toBeNull();
|
||||
expect(hash3).not.toBe('');
|
||||
expect(hash1).not.toEqual(hash2);
|
||||
expect(hash1).not.toEqual(hash3);
|
||||
expect(hash2).not.toEqual(hash3);
|
||||
});
|
||||
|
||||
it("should work if created without parameters", () => {
|
||||
const hash1 = new ParamHash().toString();
|
||||
const hash2 = new ParamHash().toString();
|
||||
|
||||
expect(hash1).not.toBeNull();
|
||||
expect(hash1).not.toBe('');
|
||||
expect(hash1).toEqual(hash2);
|
||||
});
|
||||
|
||||
it("should create the same hash if created with the same set of parameters in the same order", () => {
|
||||
const params = ['azerty', true, 23, { "a": { "b": ['azerty', true] }, "c": 23 }];
|
||||
const hash1 = new ParamHash(...params).toString();
|
||||
const hash2 = new ParamHash(...params).toString();
|
||||
|
||||
expect(hash1).toEqual(hash2);
|
||||
});
|
||||
|
||||
it("should create a different hash if created with the same set of parameters in a different order", () => {
|
||||
const params = ['azerty', true, 23, { "a": { "b": ['azerty', true] }, "c": 23 }];
|
||||
const hash1 = new ParamHash(...params).toString();
|
||||
const hash2 = new ParamHash(...params.reverse()).toString();
|
||||
|
||||
expect(hash1).not.toEqual(hash2);
|
||||
});
|
||||
});
|
35
src/app/core/shared/param-hash.ts
Normal file
35
src/app/core/shared/param-hash.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Md5 } from "ts-md5/dist/md5";
|
||||
|
||||
/**
|
||||
* Creates a hash of a set of parameters
|
||||
*/
|
||||
export class ParamHash {
|
||||
private params: Array<any>;
|
||||
|
||||
constructor(...params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an md5 hash based on the
|
||||
* params passed to the constructor
|
||||
*
|
||||
* If you hash the same set of params in the
|
||||
* same order the hashes will be identical
|
||||
*
|
||||
* @return {string}
|
||||
* an md5 hash
|
||||
*/
|
||||
toString(): string {
|
||||
let hash = new Md5();
|
||||
this.params.forEach((param) => {
|
||||
if (param === Object(param)) {
|
||||
hash.appendStr(JSON.stringify(param));
|
||||
}
|
||||
else {
|
||||
hash.appendStr('' + param);
|
||||
}
|
||||
});
|
||||
return hash.end().toString();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user