fixed an issue where a rehydrate would sometimes leave the app in a broken state

This commit is contained in:
Art Lowel
2017-10-26 12:58:42 +02:00
parent 94272b9b99
commit ee84399d23
7 changed files with 41 additions and 36 deletions

View File

@@ -36,16 +36,16 @@
<ds-error *ngIf="(collectionData | async)?.hasFailed" message="{{'error.collection' | translate}}"></ds-error> <ds-error *ngIf="(collectionData | async)?.hasFailed" message="{{'error.collection' | translate}}"></ds-error>
<ds-loading *ngIf="(collectionData | async)?.isLoading" message="{{'loading.collection' | translate}}"></ds-loading> <ds-loading *ngIf="(collectionData | async)?.isLoading" message="{{'loading.collection' | translate}}"></ds-loading>
<br> <br>
<div *ngIf="itemData?.hasSucceeded" @fadeIn> <div *ngIf="(itemData | async)?.hasSucceeded" @fadeIn>
<h2>{{'collection.page.browse.recent.head' | translate}}</h2> <h2>{{'collection.page.browse.recent.head' | translate}}</h2>
<ds-object-list <ds-object-list
[config]="paginationConfig" [config]="paginationConfig"
[sortConfig]="sortConfig" [sortConfig]="sortConfig"
[objects]="itemData" [objects]="(itemData | async)"
[hideGear]="false" [hideGear]="false"
(paginationChange)="onPaginationChange($event)"> (paginationChange)="onPaginationChange($event)">
</ds-object-list> </ds-object-list>
</div> </div>
<ds-error *ngIf="itemData?.hasFailed" message="{{'error.recent-submissions' | translate}}"></ds-error> <ds-error *ngIf="(itemData | async)?.hasFailed" message="{{'error.recent-submissions' | translate}}"></ds-error>
<ds-loading *ngIf="!itemData || itemData.isLoading" message="{{'loading.recent-submissions' | translate}}"></ds-loading> <ds-loading *ngIf="!(itemData | async) || (itemData | async).isLoading" message="{{'loading.recent-submissions' | translate}}"></ds-loading>
</div> </div>

View File

@@ -30,7 +30,7 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp
}) })
export class CollectionPageComponent implements OnInit, OnDestroy { export class CollectionPageComponent implements OnInit, OnDestroy {
collectionData: Observable<RemoteData<Collection>>; collectionData: Observable<RemoteData<Collection>>;
itemData: RemoteData<Item[]>; itemData: Observable<RemoteData<Item[]>>;
logoData: Observable<RemoteData<Bitstream>>; logoData: Observable<RemoteData<Bitstream>>;
paginationConfig: PaginationComponentOptions; paginationConfig: PaginationComponentOptions;
sortConfig: SortOptions; sortConfig: SortOptions;
@@ -87,14 +87,12 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
} }
updatePage(searchOptions) { updatePage(searchOptions) {
this.subs.push(this.itemDataService.findAll({ this.itemData = this.itemDataService.findAll({
scopeID: this.collectionId, scopeID: this.collectionId,
currentPage: searchOptions.pagination.currentPage, currentPage: searchOptions.pagination.currentPage,
elementsPerPage: searchOptions.pagination.pageSize, elementsPerPage: searchOptions.pagination.pageSize,
sort: searchOptions.sort sort: searchOptions.sort
}).subscribe((rd: RemoteData<Item[]>) => { });
this.itemData = rd;
}));
} }
ngOnDestroy(): void { ngOnDestroy(): void {

View File

@@ -1,32 +1,32 @@
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
HostListener,
Inject, Inject,
ViewEncapsulation,
OnInit, OnInit,
HostListener, SimpleChanges, OnChanges ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { TransferState } from '../modules/transfer-state/transfer-state'; import { TranslateService } from '@ngx-translate/core';
import { HostWindowState } from './shared/host-window.reducer';
import { HostWindowResizeAction } from './shared/host-window.actions';
import { NativeWindowRef, NativeWindowService } from './shared/window.service';
import { MetadataService } from './core/metadata/metadata.service';
import { GLOBAL_CONFIG, GlobalConfig } from '../config'; import { GLOBAL_CONFIG, GlobalConfig } from '../config';
import { TransferState } from '../modules/transfer-state/transfer-state';
import { MetadataService } from './core/metadata/metadata.service';
import { HostWindowResizeAction } from './shared/host-window.actions';
import { HostWindowState } from './shared/host-window.reducer';
import { NativeWindowRef, NativeWindowService } from './shared/window.service';
@Component({ @Component({
selector: 'ds-app', selector: 'ds-app',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'], styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.Default, changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class AppComponent implements OnInit, OnChanges { export class AppComponent implements OnInit {
constructor( constructor(
@Inject(GLOBAL_CONFIG) public config: GlobalConfig, @Inject(GLOBAL_CONFIG) public config: GlobalConfig,
@@ -71,9 +71,4 @@ export class AppComponent implements OnInit, OnChanges {
); );
} }
ngOnChanges(changes: SimpleChanges): void {
console.log('AppComponent: onchanges called', changes);
}
} }

View File

@@ -1,6 +1,5 @@
import { Observable } from 'rxjs/Observable';
import { PageInfo } from '../shared/page-info.model'; import { PageInfo } from '../shared/page-info.model';
import { hasValue } from '../../shared/empty.util';
export enum RemoteDataState { export enum RemoteDataState {
RequestPending = 'RequestPending', RequestPending = 'RequestPending',
@@ -26,13 +25,13 @@ export class RemoteData<T> {
} }
get state(): RemoteDataState { get state(): RemoteDataState {
if (this.isSuccessFul === true) { if (this.isSuccessFul === true && hasValue(this.payload)) {
return RemoteDataState.Success return RemoteDataState.Success
} else if (this.isSuccessFul === false) { } else if (this.isSuccessFul === false) {
return RemoteDataState.Failed return RemoteDataState.Failed
} else if (this.requestPending === true) { } else if (this.requestPending === true) {
return RemoteDataState.RequestPending return RemoteDataState.RequestPending
} else if (this.responsePending === true || this.isSuccessFul === undefined) { } else {
return RemoteDataState.ResponsePending return RemoteDataState.ResponsePending
} }
} }

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { MemoizedSelector, Store } from '@ngrx/store'; import { createSelector, MemoizedSelector, Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
@@ -9,17 +9,24 @@ import { ObjectCacheService } from '../cache/object-cache.service';
import { DSOSuccessResponse, RestResponse } from '../cache/response-cache.models'; import { DSOSuccessResponse, RestResponse } from '../cache/response-cache.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service'; import { ResponseCacheService } from '../cache/response-cache.service';
import { 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 } from './request.models';
import { RequestEntry } from './request.reducer'; import { RequestEntry, RequestState } from './request.reducer';
function entryFromHrefSelector(href: string): MemoizedSelector<CoreState, RequestEntry> { function entryFromHrefSelector(href: string): MemoizedSelector<CoreState, RequestEntry> {
return keySelector<RequestEntry>('data/request', href); return keySelector<RequestEntry>('data/request', href);
} }
export function requestStateSelector(): MemoizedSelector<CoreState, RequestState> {
return createSelector(coreSelector, (state: CoreState) => {
return state['data/request'] as RequestState;
});
}
@Injectable() @Injectable()
export class RequestService { export class RequestService {
private requestsOnTheirWayToTheStore: string[] = []; private requestsOnTheirWayToTheStore: string[] = [];
@@ -54,7 +61,6 @@ export class RequestService {
configure<T extends CacheableObject>(request: RestRequest): void { configure<T extends CacheableObject>(request: RestRequest): void {
let isCached = this.objectCache.hasBySelfLink(request.href); let isCached = this.objectCache.hasBySelfLink(request.href);
// console.log('request.href', 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)
.take(1) .take(1)

View File

@@ -17,7 +17,6 @@ export abstract class HALEndpointService {
const request = new RootEndpointRequest(this.EnvConfig); const request = new RootEndpointRequest(this.EnvConfig);
this.requestService.configure(request); this.requestService.configure(request);
return this.responseCache.get(request.href) return this.responseCache.get(request.href)
// .do((entry: ResponseCacheEntry) => console.log('entry.response', entry.response))
.map((entry: ResponseCacheEntry) => entry.response) .map((entry: ResponseCacheEntry) => entry.response)
.filter((response: RootSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap)) .filter((response: RootSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap))
.map((response: RootSuccessResponse) => response.endpointMap) .map((response: RootSuccessResponse) => response.endpointMap)

View File

@@ -36,7 +36,15 @@ export function boot(cache: TransferState, appRef: ApplicationRef, store: Store<
// authentication mechanism goes here // authentication mechanism goes here
return () => { return () => {
appRef.isStable.filter((stable: boolean) => stable).first().subscribe(() => { appRef.isStable.filter((stable: boolean) => stable).first().subscribe(() => {
// isStable == true doesn't guarantee that all dispatched actions have been
// processed yet. So in those cases the store snapshot wouldn't be complete
// and a rehydrate would leave the app in a broken state
//
// This setTimeout without delay schedules the cache.inject() to happen ASAP
// after everything that's already scheduled, and it solves that problem.
setTimeout(() => {
cache.inject(); cache.inject();
}, 0);
}); });
}; };
} }