import { Resource, getUrl, hasLink } from '@ngxp/rest'; import { isEqual, isNull } from 'lodash-es'; import { Observable, combineLatest, filter, startWith, tap } from 'rxjs'; import { ResourceStateService } from '../ngrx/state.service'; import { isNotNull } from '../tech.util'; import { ServiceConfig } from './resource.model'; import { mapToFirst, mapToResource } from './resource.rxjs.operator'; import { StateResource, createEmptyStateResource, isInvalidResourceCombination, isLoadingRequired, isStateResoureStable, } from './resource.util'; export class ResourceLoader<B extends Resource, T extends Resource> { configResource: B = null; constructor( private config: ServiceConfig<B>, private resourceStateService: ResourceStateService<B, T>, ) {} public get(): Observable<StateResource<T>> { return combineLatest([ this.resourceStateService.selectResource(), this.getConfigResource(), ]).pipe( tap(([stateResource, configResource]) => this.handleResourceChanges(stateResource, configResource), ), filter( ([stateResource, configResource]) => !isInvalidResourceCombination(stateResource, configResource), ), mapToFirst<T, B>(), startWith(createEmptyStateResource<T>(true)), ); } private getConfigResource(): Observable<B> { return this.config.baseResource.pipe(filter(isStateResoureStable), mapToResource<B>()); } handleResourceChanges(stateResource: StateResource<T>, configResource: B): void { if (!isEqual(this.configResource, configResource)) { this.handleConfigResourceChanges(stateResource, configResource); } else if (this.shouldLoadResource(stateResource, configResource)) { this.loadResource(configResource); } } handleConfigResourceChanges(stateResource: StateResource<T>, configResource: B) { this.configResource = configResource; if (isStateResoureStable(stateResource)) { this.updateStateResourceByConfigResource(stateResource, configResource); } } updateStateResourceByConfigResource(stateResource: StateResource<T>, configResource: B): void { if (this.shouldClearStateResource(stateResource, configResource)) { this.resourceStateService.clearResource(); } else if (this.hasGetLink(configResource)) { this.loadResource(configResource); } } shouldClearStateResource(stateResource: StateResource<T>, configResource: B): boolean { return ( (isNull(configResource) || this.hasNotGetLink(configResource)) && !this.isStateResourceEmpty(stateResource) ); } private hasNotGetLink(configResource: B) { return !this.hasGetLink(configResource); } hasGetLink(configResource: B): boolean { return isNotNull(configResource) && hasLink(configResource, this.config.getLinkRel); } private isStateResourceEmpty(stateResource: StateResource<T>): boolean { return isEqual(stateResource, createEmptyStateResource()); } shouldLoadResource(stateResource: StateResource<T>, configResource: B): boolean { return ( isNotNull(configResource) && hasLink(configResource, this.config.getLinkRel) && isLoadingRequired(stateResource) ); } loadResource(configResource: B): void { this.resourceStateService.loadResource(this.getGetUrl(configResource)); } private getGetUrl(configResource: B): string { return getUrl(configResource, this.config.getLinkRel); } }