cleaned up remotedata for a better separation of concerns, moved statuscode and errormsg in to RemoteDataError object, moved pageInfo to PaginatedList object in the payload

This commit is contained in:
Art Lowel
2017-12-07 11:28:44 +01:00
parent 98a49b3191
commit d775467fcb
17 changed files with 162 additions and 128 deletions

View File

@@ -6,6 +6,7 @@ import { Subscription } from 'rxjs/Subscription';
import { SortOptions } from '../core/cache/models/sort-options.model'; import { SortOptions } from '../core/cache/models/sort-options.model';
import { CollectionDataService } from '../core/data/collection-data.service'; import { CollectionDataService } from '../core/data/collection-data.service';
import { ItemDataService } from '../core/data/item-data.service'; import { ItemDataService } from '../core/data/item-data.service';
import { PaginatedList } from '../core/data/paginated-list';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { MetadataService } from '../core/metadata/metadata.service'; import { MetadataService } from '../core/metadata/metadata.service';
@@ -30,7 +31,7 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp
}) })
export class CollectionPageComponent implements OnInit, OnDestroy { export class CollectionPageComponent implements OnInit, OnDestroy {
collectionRDObs: Observable<RemoteData<Collection>>; collectionRDObs: Observable<RemoteData<Collection>>;
itemRDObs: Observable<RemoteData<Item[]>>; itemRDObs: Observable<RemoteData<PaginatedList<Item>>>;
logoRDObs: Observable<RemoteData<Bitstream>>; logoRDObs: Observable<RemoteData<Bitstream>>;
paginationConfig: PaginationComponentOptions; paginationConfig: PaginationComponentOptions;
sortConfig: SortOptions; sortConfig: SortOptions;

View File

@@ -2,7 +2,7 @@
<div *ngIf="subCollectionsRD?.hasSucceeded" @fadeIn> <div *ngIf="subCollectionsRD?.hasSucceeded" @fadeIn>
<h2>{{'community.sub-collection-list.head' | translate}}</h2> <h2>{{'community.sub-collection-list.head' | translate}}</h2>
<ul> <ul>
<li *ngFor="let collection of subCollectionsRD?.payload"> <li *ngFor="let collection of subCollectionsRD?.payload?.page">
<p> <p>
<span class="lead"><a [routerLink]="['/collections', collection.id]">{{collection.name}}</a></span><br> <span class="lead"><a [routerLink]="['/collections', collection.id]">{{collection.name}}</a></span><br>
<span class="text-muted">{{collection.shortDescription}}</span> <span class="text-muted">{{collection.shortDescription}}</span>

View File

@@ -1,11 +1,12 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { CollectionDataService } from '../../core/data/collection-data.service'; import { CollectionDataService } from '../../core/data/collection-data.service';
import { PaginatedList } from '../../core/data/paginated-list';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { fadeIn } from '../../shared/animations/fade'; import { fadeIn } from '../../shared/animations/fade';
import { Observable } from 'rxjs/Observable';
@Component({ @Component({
selector: 'ds-community-page-sub-collection-list', selector: 'ds-community-page-sub-collection-list',
@@ -14,7 +15,7 @@ import { Observable } from 'rxjs/Observable';
animations:[fadeIn] animations:[fadeIn]
}) })
export class CommunityPageSubCollectionListComponent implements OnInit { export class CommunityPageSubCollectionListComponent implements OnInit {
subCollectionsRDObs: Observable<RemoteData<Collection[]>>; subCollectionsRDObs: Observable<RemoteData<PaginatedList<Collection>>>;
constructor(private cds: CollectionDataService) { constructor(private cds: CollectionDataService) {

View File

@@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { SortOptions } from '../../core/cache/models/sort-options.model'; import { SortOptions } from '../../core/cache/models/sort-options.model';
import { CommunityDataService } from '../../core/data/community-data.service'; import { CommunityDataService } from '../../core/data/community-data.service';
import { PaginatedList } from '../../core/data/paginated-list';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
@@ -17,7 +18,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
animations: [fadeInOut] animations: [fadeInOut]
}) })
export class TopLevelCommunityListComponent { export class TopLevelCommunityListComponent {
communitiesRDObs: Observable<RemoteData<Community[]>>; communitiesRDObs: Observable<RemoteData<PaginatedList<Community>>>;
config: PaginationComponentOptions; config: PaginationComponentOptions;
sortConfig: SortOptions; sortConfig: SortOptions;

View File

@@ -8,7 +8,7 @@
[query]="query" [query]="query"
[scope]="(scopeObjectRDObs | async)?.payload" [scope]="(scopeObjectRDObs | async)?.payload"
[currentParams]="currentParams" [currentParams]="currentParams"
[scopes]="(scopeListRDObs | async)?.payload"> [scopes]="(scopeListRDObs | async)?.payload?.page">
</ds-search-form> </ds-search-form>
<div class="row"> <div class="row">
<div id="search-body" <div id="search-body"
@@ -29,7 +29,7 @@
| translate}} | translate}}
</button> </button>
</div> </div>
<ds-search-results [searchResults]="resultsRDObs | async" <ds-search-results [searchResults]="(resultsRDObs | async)?.page"
[searchConfig]="searchOptions"></ds-search-results> [searchConfig]="searchOptions"></ds-search-results>
</div> </div>
</div> </div>

View File

@@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { CommunityDataService } from '../core/data/community-data.service'; import { CommunityDataService } from '../core/data/community-data.service';
import { PaginatedList } from '../core/data/paginated-list';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { Community } from '../core/shared/community.model'; import { Community } from '../core/shared/community.model';
import { DSpaceObject } from '../core/shared/dspace-object.model'; import { DSpaceObject } from '../core/shared/dspace-object.model';
@@ -36,7 +37,7 @@ export class SearchPageComponent implements OnInit, OnDestroy {
resultsRDObs: Observable<RemoteData<Array<SearchResult<DSpaceObject>>>>; resultsRDObs: Observable<RemoteData<Array<SearchResult<DSpaceObject>>>>;
currentParams = {}; currentParams = {};
searchOptions: SearchOptions; searchOptions: SearchOptions;
scopeListRDObs: Observable<RemoteData<Community[]>>; scopeListRDObs: Observable<RemoteData<PaginatedList<Community>>>;
isMobileView: Observable<boolean>; isMobileView: Observable<boolean>;
constructor(private service: SearchService, constructor(private service: SearchService,

View File

@@ -1,6 +1,8 @@
import { Injectable, OnDestroy } from '@angular/core'; import { Injectable, OnDestroy } from '@angular/core';
import { PaginatedList } from '../../core/data/paginated-list';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { RemoteDataError } from '../../core/data/remote-data-error';
import { SearchResult } from '../search-result.model'; import { SearchResult } from '../search-result.model';
import { ItemDataService } from '../../core/data/item-data.service'; import { ItemDataService } from '../../core/data/item-data.service';
import { PageInfo } from '../../core/shared/page-info.model'; import { PageInfo } from '../../core/shared/page-info.model';
@@ -100,26 +102,7 @@ export class SearchService implements OnDestroy {
} }
search(query: string, scopeId?: string, searchOptions?: SearchOptions): Observable<RemoteData<Array<SearchResult<DSpaceObject>>>> { search(query: string, scopeId?: string, searchOptions?: SearchOptions): Observable<RemoteData<Array<SearchResult<DSpaceObject>>>> {
this.searchOptions = this.searchOptions; const error = new RemoteDataError('200', undefined);
let self = `https://dspace7.4science.it/dspace-spring-rest/api/search?query=${query}`;
if (hasValue(scopeId)) {
self += `&scope=${scopeId}`;
}
if (isNotEmpty(searchOptions) && hasValue(searchOptions.pagination.currentPage)) {
self += `&page=${searchOptions.pagination.currentPage}`;
}
if (isNotEmpty(searchOptions) && hasValue(searchOptions.pagination.pageSize)) {
self += `&pageSize=${searchOptions.pagination.pageSize}`;
}
if (isNotEmpty(searchOptions) && hasValue(searchOptions.sort.direction)) {
self += `&sortDirection=${searchOptions.sort.direction}`;
}
if (isNotEmpty(searchOptions) && hasValue(searchOptions.sort.field)) {
self += `&sortField=${searchOptions.sort.field}`;
}
const errorMessage = undefined;
const statusCode = '200';
const returningPageInfo = new PageInfo(); const returningPageInfo = new PageInfo();
if (isNotEmpty(searchOptions)) { if (isNotEmpty(searchOptions)) {
@@ -137,13 +120,12 @@ export class SearchService implements OnDestroy {
}); });
return itemsObs return itemsObs
.filter((rd: RemoteData<Item[]>) => rd.hasSucceeded) .filter((rd: RemoteData<PaginatedList<Item>>) => rd.hasSucceeded)
.map((rd: RemoteData<Item[]>) => { .map((rd: RemoteData<PaginatedList<Item>>) => {
const totalElements = rd.pageInfo.totalElements > 20 ? 20 : rd.pageInfo.totalElements; const totalElements = rd.payload.totalElements > 20 ? 20 : rd.payload.totalElements;
const pageInfo = Object.assign({}, rd.pageInfo, { totalElements: totalElements });
const payload = shuffle(rd.payload) const page = shuffle(rd.payload.page)
.map((item: Item, index: number) => { .map((item: Item, index: number) => {
const mockResult: SearchResult<DSpaceObject> = new ItemSearchResult(); const mockResult: SearchResult<DSpaceObject> = new ItemSearchResult();
mockResult.dspaceObject = item; mockResult.dspaceObject = item;
@@ -154,24 +136,20 @@ export class SearchService implements OnDestroy {
return mockResult; return mockResult;
}); });
const payload = Object.assign({}, rd.payload, { totalElements: totalElements, page });
return new RemoteData( return new RemoteData(
self,
rd.isRequestPending, rd.isRequestPending,
rd.isResponsePending, rd.isResponsePending,
rd.hasSucceeded, rd.hasSucceeded,
errorMessage, error,
statusCode,
pageInfo,
payload payload
) )
}).startWith(new RemoteData( }).startWith(new RemoteData(
'',
true, true,
false, false,
undefined, undefined,
undefined, undefined,
undefined,
undefined,
undefined undefined
)); ));
} }
@@ -180,17 +158,12 @@ export class SearchService implements OnDestroy {
const requestPending = false; const requestPending = false;
const responsePending = false; const responsePending = false;
const isSuccessful = true; const isSuccessful = true;
const errorMessage = undefined; const error = new RemoteDataError('200', undefined);
const statusCode = '200';
const returningPageInfo = new PageInfo();
return Observable.of(new RemoteData( return Observable.of(new RemoteData(
'https://dspace7.4science.it/dspace-spring-rest/api/search',
requestPending, requestPending,
responsePending, responsePending,
isSuccessful, isSuccessful,
errorMessage, error,
statusCode,
returningPageInfo,
this.config this.config
)); ));
} }
@@ -198,12 +171,12 @@ export class SearchService implements OnDestroy {
getFacetValuesFor(searchFilterConfigName: string): Observable<RemoteData<FacetValue[]>> { getFacetValuesFor(searchFilterConfigName: string): Observable<RemoteData<FacetValue[]>> {
const filterConfig = this.config.find((config: SearchFilterConfig) => config.name === searchFilterConfigName); const filterConfig = this.config.find((config: SearchFilterConfig) => config.name === searchFilterConfigName);
return this.routeService.getQueryParameterValues(filterConfig.paramName).map((selectedValues: string[]) => { return this.routeService.getQueryParameterValues(filterConfig.paramName).map((selectedValues: string[]) => {
const values: FacetValue[] = []; const payload: FacetValue[] = [];
const totalFilters = 13; const totalFilters = 13;
for (let i = 0; i < totalFilters; i++) { for (let i = 0; i < totalFilters; i++) {
const value = searchFilterConfigName + ' ' + (i + 1); const value = searchFilterConfigName + ' ' + (i + 1);
if (!selectedValues.includes(value)) { if (!selectedValues.includes(value)) {
values.push({ payload.push({
value: value, value: value,
count: Math.floor(Math.random() * 20) + 20 * (totalFilters - i), // make sure first results have the highest (random) count count: Math.floor(Math.random() * 20) + 20 * (totalFilters - i), // make sure first results have the highest (random) count
search: decodeURI(this.router.url) + (this.router.url.includes('?') ? '&' : '?') + filterConfig.paramName + '=' + value search: decodeURI(this.router.url) + (this.router.url.includes('?') ? '&' : '?') + filterConfig.paramName + '=' + value
@@ -213,18 +186,13 @@ export class SearchService implements OnDestroy {
const requestPending = false; const requestPending = false;
const responsePending = false; const responsePending = false;
const isSuccessful = true; const isSuccessful = true;
const errorMessage = undefined; const error = new RemoteDataError('200', undefined);;
const statusCode = '200';
const returningPageInfo = new PageInfo();
return new RemoteData( return new RemoteData(
'https://dspace7.4science.it/dspace-spring-rest/api/search',
requestPending, requestPending,
responsePending, responsePending,
isSuccessful, isSuccessful,
errorMessage, error,
statusCode, payload
returningPageInfo,
values
) )
} }
) )

View File

@@ -1,5 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { PaginatedList } from '../../data/paginated-list';
import { RemoteDataError } from '../../data/remote-data-error';
import { CacheableObject } from '../object-cache.reducer'; import { CacheableObject } from '../object-cache.reducer';
import { ObjectCacheService } from '../object-cache.service'; import { ObjectCacheService } from '../object-cache.service';
@@ -88,32 +90,18 @@ export class RemoteDataBuildService {
const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true; const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true;
const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false; const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false;
let isSuccessFul: boolean; let isSuccessFul: boolean;
let errorMessage: string; let error: RemoteDataError;
let statusCode: string;
let pageInfo: PageInfo;
if (hasValue(resEntry) && hasValue(resEntry.response)) { if (hasValue(resEntry) && hasValue(resEntry.response)) {
isSuccessFul = resEntry.response.isSuccessful; isSuccessFul = resEntry.response.isSuccessful;
errorMessage = isSuccessFul === false ? (resEntry.response as ErrorResponse).errorMessage : undefined; const errorMessage = isSuccessFul === false ? (resEntry.response as ErrorResponse).errorMessage : undefined;
statusCode = resEntry.response.statusCode; error = new RemoteDataError(resEntry.response.statusCode, errorMessage);
if (hasValue((resEntry.response as DSOSuccessResponse).pageInfo)) {
const resPageInfo = (resEntry.response as DSOSuccessResponse).pageInfo;
if (isNotEmpty(resPageInfo) && resPageInfo.currentPage >= 0) {
pageInfo = Object.assign({}, resPageInfo, { currentPage: resPageInfo.currentPage + 1 });
} else {
pageInfo = resPageInfo;
}
}
} }
return new RemoteData( return new RemoteData(
href,
requestPending, requestPending,
responsePending, responsePending,
isSuccessFul, isSuccessFul,
errorMessage, error,
statusCode,
pageInfo,
payload payload
); );
}); });
@@ -122,7 +110,7 @@ export class RemoteDataBuildService {
buildList<TNormalized extends CacheableObject, TDomain>( buildList<TNormalized extends CacheableObject, TDomain>(
hrefObs: string | Observable<string>, hrefObs: string | Observable<string>,
normalizedType: GenericConstructor<TNormalized> normalizedType: GenericConstructor<TNormalized>
): Observable<RemoteData<TDomain[]>> { ): Observable<RemoteData<TDomain[] | PaginatedList<TDomain>>> {
if (typeof hrefObs === 'string') { if (typeof hrefObs === 'string') {
hrefObs = Observable.of(hrefObs); hrefObs = Observable.of(hrefObs);
} }
@@ -132,7 +120,7 @@ export class RemoteDataBuildService {
const responseCacheObs = hrefObs.flatMap((href: string) => this.responseCache.get(href)) const responseCacheObs = hrefObs.flatMap((href: string) => this.responseCache.get(href))
.filter((entry) => hasValue(entry)); .filter((entry) => hasValue(entry));
const payloadObs = responseCacheObs const tDomainListObs = responseCacheObs
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful) .filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).resourceSelfLinks) .map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).resourceSelfLinks)
.flatMap((resourceUUIDs: string[]) => { .flatMap((resourceUUIDs: string[]) => {
@@ -146,6 +134,27 @@ export class RemoteDataBuildService {
.startWith([]) .startWith([])
.distinctUntilChanged(); .distinctUntilChanged();
const pageInfoObs = responseCacheObs
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => {
if (hasValue((entry.response as DSOSuccessResponse).pageInfo)) {
const resPageInfo = (entry.response as DSOSuccessResponse).pageInfo;
if (isNotEmpty(resPageInfo) && resPageInfo.currentPage >= 0) {
return Object.assign({}, resPageInfo, { currentPage: resPageInfo.currentPage + 1 });
} else {
return resPageInfo;
}
}
});
const payloadObs = Observable.combineLatest(tDomainListObs, pageInfoObs, (tDomainList, pageInfo) => {
if (hasValue(pageInfo)) {
return new PaginatedList(pageInfo, tDomainList);
} else {
return tDomainList;
}
});
return this.toRemoteDataObservable(hrefObs, requestObs, responseCacheObs, payloadObs); return this.toRemoteDataObservable(hrefObs, requestObs, responseCacheObs, payloadObs);
} }
@@ -209,35 +218,32 @@ export class RemoteDataBuildService {
.every((b: boolean) => b === true); .every((b: boolean) => b === true);
const errorMessage: string = arr const errorMessage: string = arr
.map((d: RemoteData<T>) => d.errorMessage) .map((d: RemoteData<T>) => d.error)
.map((e: string, idx: number) => { .map((e: RemoteDataError, idx: number) => {
if (hasValue(e)) { if (hasValue(e)) {
return `[${idx}]: ${e}`; return `[${idx}]: ${e.message}`;
} }
}).filter((e: string) => hasValue(e)) }).filter((e: string) => hasValue(e))
.join(', '); .join(', ');
const statusCode: string = arr const statusCode: string = arr
.map((d: RemoteData<T>) => d.statusCode) .map((d: RemoteData<T>) => d.error)
.map((c: string, idx: number) => { .map((e: RemoteDataError, idx: number) => {
if (hasValue(c)) { if (hasValue(e)) {
return `[${idx}]: ${c}`; return `[${idx}]: ${e.statusCode}`;
} }
}).filter((c: string) => hasValue(c)) }).filter((c: string) => hasValue(c))
.join(', '); .join(', ');
const pageInfo = undefined; const error = new RemoteDataError(statusCode, errorMessage);
const payload: T[] = arr.map((d: RemoteData<T>) => d.payload); const payload: T[] = arr.map((d: RemoteData<T>) => d.payload);
return new RemoteData( return new RemoteData(
`dspace-angular://aggregated/object/${new Date().getTime()}`,
requestPending, requestPending,
responsePending, responsePending,
isSuccessFul, isSuccessFul,
errorMessage, error,
statusCode,
pageInfo,
payload payload
); );
}) })

View File

@@ -10,6 +10,7 @@ import { DSpaceObject } from '../shared/dspace-object.model';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
import { URLCombiner } from '../url-combiner/url-combiner'; import { URLCombiner } from '../url-combiner/url-combiner';
import { PaginatedList } from './paginated-list';
import { RemoteData } from './remote-data'; import { RemoteData } from './remote-data';
import { import {
FindAllOptions, FindAllOptions,
@@ -70,7 +71,7 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
} }
} }
findAll(options: FindAllOptions = {}): Observable<RemoteData<TDomain[]>> { findAll(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> {
const hrefObs = this.getEndpoint().filter((href: string) => isNotEmpty(href)) const hrefObs = this.getEndpoint().filter((href: string) => isNotEmpty(href))
.flatMap((endpoint: string) => this.getFindAllHref(endpoint, options)); .flatMap((endpoint: string) => this.getFindAllHref(endpoint, options));
@@ -82,7 +83,7 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
this.requestService.configure(request); this.requestService.configure(request);
}); });
return this.rdbService.buildList<TNormalized, TDomain>(hrefObs, this.normalizedResourceType); return this.rdbService.buildList<TNormalized, TDomain>(hrefObs, this.normalizedResourceType) as Observable<RemoteData<PaginatedList<TDomain>>>;
} }
getFindByIDHref(endpoint, resourceID): string { getFindByIDHref(endpoint, resourceID): string {

View File

@@ -0,0 +1,42 @@
import { PageInfo } from '../shared/page-info.model';
export class PaginatedList<T> {
constructor(
private pageInfo: PageInfo,
public page: T[]
) {
}
get elementsPerPage(): number {
return this.pageInfo.elementsPerPage;
}
set elementsPerPage(value: number) {
this.pageInfo.elementsPerPage = value;
}
get totalElements(): number {
return this.pageInfo.totalElements;
}
set totalElements(value: number) {
this.pageInfo.totalElements = value;
}
get totalPages(): number {
return this.pageInfo.totalPages;
}
set totalPages(value: number) {
this.pageInfo.totalPages = value;
}
get currentPage(): number {
return this.pageInfo.currentPage;
}
set currentPage(value: number) {
this.pageInfo.currentPage = value;
}
}

View File

@@ -0,0 +1,7 @@
export class RemoteDataError {
constructor(
public statusCode: string,
public message: string
) {
}
}

View File

@@ -1,5 +1,5 @@
import { PageInfo } from '../shared/page-info.model';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { RemoteDataError } from './remote-data-error';
export enum RemoteDataState { export enum RemoteDataState {
RequestPending = 'RequestPending', RequestPending = 'RequestPending',
@@ -13,13 +13,10 @@ export enum RemoteDataState {
*/ */
export class RemoteData<T> { export class RemoteData<T> {
constructor( constructor(
public self: string,
private requestPending: boolean, private requestPending: boolean,
private responsePending: boolean, private responsePending: boolean,
private isSuccessFul: boolean, private isSuccessFul: boolean,
public errorMessage: string, public error: RemoteDataError,
public statusCode: string,
public pageInfo: PageInfo,
public payload: T public payload: T
) { ) {
} }

View File

@@ -9,14 +9,24 @@ import { BrowseResponseParsingService } from './browse-response-parsing.service'
import { ConfigResponseParsingService } from './config-response-parsing.service'; import { ConfigResponseParsingService } from './config-response-parsing.service';
/* tslint:disable:max-classes-per-file */ /* tslint:disable:max-classes-per-file */
/**
* Represents a Request Method.
*
* I didn't reuse the RequestMethod enum in @angular/http because
* it uses numbers. The string values here are more clear when
* debugging.
*
* The ones commented out are still unsupported in the rest of the codebase
*/
export enum RestRequestMethod { export enum RestRequestMethod {
Get = 'GET', Get = 'GET',
Post = 'POST', Post = 'POST',
Put = 'PUT', // Put = 'PUT',
Delete = 'DELETE', // Delete = 'DELETE',
Options = 'OPTIONS', // Options = 'OPTIONS',
Head = 'HEAD', // Head = 'HEAD',
Patch = 'PATCH' // Patch = 'PATCH'
} }
export class RestRequest { export class RestRequest {

View File

@@ -12,7 +12,7 @@ import { ResponseCacheService } from '../cache/response-cache.service';
import { coreSelector, CoreState } from '../core.reducers'; import { coreSelector, CoreState } from '../core.reducers';
import { keySelector } from '../shared/selectors'; import { keySelector } from '../shared/selectors';
import { RequestConfigureAction, RequestExecuteAction } from './request.actions'; import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
import { RestRequest } from './request.models'; import { RestRequest, RestRequestMethod } from './request.models';
import { RequestEntry, RequestState } from './request.reducer'; import { RequestEntry, RequestState } from './request.reducer';
@@ -30,11 +30,9 @@ export function requestStateSelector(): MemoizedSelector<CoreState, RequestState
export class RequestService { export class RequestService {
private requestsOnTheirWayToTheStore: string[] = []; private requestsOnTheirWayToTheStore: string[] = [];
constructor( constructor(private objectCache: ObjectCacheService,
private objectCache: ObjectCacheService, private responseCache: ResponseCacheService,
private responseCache: ResponseCacheService, private store: Store<CoreState>) {
private store: Store<CoreState>
) {
} }
isPending(href: string): boolean { isPending(href: string): boolean {
@@ -59,6 +57,12 @@ export class RequestService {
} }
configure<T extends CacheableObject>(request: RestRequest): void { configure<T extends CacheableObject>(request: RestRequest): void {
if (request.method !== RestRequestMethod.Get || !this.isCachedOrPending(request)) {
this.dispatchRequest(request);
}
}
private isCachedOrPending(request: RestRequest) {
let isCached = this.objectCache.hasBySelfLink(request.href); let isCached = this.objectCache.hasBySelfLink(request.href);
if (!isCached && this.responseCache.has(request.href)) { if (!isCached && this.responseCache.has(request.href)) {
const [successResponse, errorResponse] = this.responseCache.get(request.href) const [successResponse, errorResponse] = this.responseCache.get(request.href)
@@ -84,11 +88,13 @@ export class RequestService {
const isPending = this.isPending(request.href); const isPending = this.isPending(request.href);
if (!(isCached || isPending)) { return isCached || isPending;
this.store.dispatch(new RequestConfigureAction(request)); }
this.store.dispatch(new RequestExecuteAction(request.href));
this.trackRequestsOnTheirWayToTheStore(request.href); private dispatchRequest(request: RestRequest) {
} this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(request.href));
this.trackRequestsOnTheirWayToTheStore(request.href);
} }
/** /**

View File

@@ -11,6 +11,7 @@ import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { Store, StoreModule } from '@ngrx/store'; import { Store, StoreModule } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { RemoteDataError } from '../data/remote-data-error';
import { MetadataService } from './metadata.service'; import { MetadataService } from './metadata.service';
@@ -178,13 +179,10 @@ describe('MetadataService', () => {
const mockRemoteData = (mockItem: Item): Observable<RemoteData<Item>> => { const mockRemoteData = (mockItem: Item): Observable<RemoteData<Item>> => {
return Observable.of(new RemoteData<Item>( return Observable.of(new RemoteData<Item>(
'',
false, false,
false, false,
true, true,
'', new RemoteDataError('200', ''),
'200',
{} as PageInfo,
MockItem MockItem
)); ));
} }

View File

@@ -1,7 +1,7 @@
<ds-pagination <ds-pagination
[paginationOptions]="config" [paginationOptions]="config"
[pageInfoState]="pageInfo" [pageInfoState]="objects?.payload"
[collectionSize]="pageInfo?.totalElements" [collectionSize]="objects?.payload?.totalElements"
[sortOptions]="sortConfig" [sortOptions]="sortConfig"
[hideGear]="hideGear" [hideGear]="hideGear"
[hidePagerWhenSinglePage]="hidePagerWhenSinglePage" [hidePagerWhenSinglePage]="hidePagerWhenSinglePage"
@@ -11,7 +11,7 @@
(sortFieldChange)="onSortFieldChange($event)" (sortFieldChange)="onSortFieldChange($event)"
(paginationChange)="onPaginationChange($event)"> (paginationChange)="onPaginationChange($event)">
<ul *ngIf="objects?.hasSucceeded"> <!--class="list-unstyled"--> <ul *ngIf="objects?.hasSucceeded"> <!--class="list-unstyled"-->
<li *ngFor="let object of objects?.payload"> <li *ngFor="let object of objects?.payload?.page">
<ds-wrapper-list-element [object]="object"></ds-wrapper-list-element> <ds-wrapper-list-element [object]="object"></ds-wrapper-list-element>
</li> </li>
</ul> </ul>

View File

@@ -8,13 +8,12 @@ import {
} from '@angular/core'; } from '@angular/core';
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
import { PaginatedList } from '../core/data/paginated-list';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { PageInfo } from '../core/shared/page-info.model'; import { ListableObject } from './listable-object/listable-object.model';
import { ListableObject } from '../object-list/listable-object/listable-object.model';
import { fadeIn } from '../shared/animations/fade'; import { fadeIn } from '../shared/animations/fade';
import { hasValue } from '../shared/empty.util';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
@@ -32,13 +31,9 @@ export class ObjectListComponent {
@Input() sortConfig: SortOptions; @Input() sortConfig: SortOptions;
@Input() hideGear = false; @Input() hideGear = false;
@Input() hidePagerWhenSinglePage = true; @Input() hidePagerWhenSinglePage = true;
private _objects: RemoteData<ListableObject[]>; private _objects: RemoteData<PaginatedList<ListableObject>>;
pageInfo: PageInfo; @Input() set objects(objects: RemoteData<PaginatedList<ListableObject>>) {
@Input() set objects(objects: RemoteData<ListableObject[]>) {
this._objects = objects; this._objects = objects;
if (hasValue(objects)) {
this.pageInfo = objects.pageInfo;
}
} }
get objects() { get objects() {
return this._objects; return this._objects;