Skip to content
Snippets Groups Projects
Select Git revision
  • e3810d3cc76dbf6639e0e1767a9fe5a3bcde0446
  • main default protected
  • release
  • 0.21.0
  • 0.20.0
  • 0.19.0
  • 0.18.0
  • 0.17.0
  • 0.16.0
  • 0.15.0
  • 0.14.0
  • 0.13.0
  • 0.11.0
  • 0.10.0
  • 0.9.0
  • 0.8.0
  • 0.7.0
  • 0.6.0
  • 0.5.0
  • 0.4.2
  • 0.4.1
  • 0.4.0
  • 0.3.0
23 results

pom.xml

Blame
  • resource.service.ts 6.08 KiB
    import {
      BehaviorSubject,
      Observable,
      catchError,
      combineLatest,
      filter,
      map,
      mergeMap,
      of,
      startWith,
      tap,
      throwError,
    } from 'rxjs';
    import { ResourceServiceConfig } from './resource.model';
    import {
      StateResource,
      createEmptyStateResource,
      createErrorStateResource,
      createStateResource,
      isLoadingRequired,
      throwErrorOn,
    } from './resource.util';
    import { Resource, getUrl, hasLink } from '@ngxp/rest';
    import { ResourceRepository } from './resource.repository';
    import { isEqual, isNull } from 'lodash-es';
    import { isNotNull } from '../tech.util';
    import { HttpErrorResponse } from '@angular/common/http';
    import { isUnprocessableEntity } from '../http.util';
    import { HttpError, ProblemDetail } from '../tech.model';
    import { isDevMode } from '@angular/core';
    
    /**
     * B = Type of baseresource
     * T = Type of the resource which is working on
     */
    export class ResourceService<B extends Resource, T extends Resource> {
      readonly stateResource: BehaviorSubject<StateResource<T>> = new BehaviorSubject(
        createEmptyStateResource(),
      );
    
      configResource: B;
    
      constructor(
        private config: ResourceServiceConfig<B>,
        private repository: ResourceRepository,
      ) {}
    
      public get(): Observable<StateResource<T>> {
        return combineLatest([this.stateResource.asObservable(), this.getConfigResource()]).pipe(
          tap(([, configResource]) => this.handleConfigResourceChanged(configResource)),
          tap(([, configResource]) => this.handleNullConfigResource(configResource)),
          tap(([stateResource, configResource]) =>
            this.handleConfigResource(stateResource, configResource),
          ),
          filter(([stateResource]) => !this.shouldFilter(stateResource)),
          map(([stateResource]) => stateResource),
          startWith(createEmptyStateResource<T>(true)),
        );
      }
    
      private getConfigResource(): Observable<B> {
        return this.config.resource.pipe(
          filter((stateResource) => stateResource.loaded && !stateResource.loading),
          map((stateResource) => stateResource.resource),
        );
      }
    
      handleConfigResourceChanged(configResource: B): void {
        if (!isEqual(this.configResource, configResource)) {
          this.configResource = configResource;
          this.stateResource.next({ ...this.stateResource.value, reload: true });
        }
      }
    
      handleNullConfigResource(configResource: B): void {
        if (this.shouldClearStateResource(configResource)) {
          this.stateResource.next(createEmptyStateResource());
        }
      }
    
      shouldClearStateResource(configResource: B): boolean {
        return isNull(configResource) && !isEqual(this.stateResource.value, createEmptyStateResource());
      }
    
      handleConfigResource(stateResource: StateResource<T>, configResource: B): void {
        if (this.shouldLoadResource(stateResource, configResource)) {
          this.loadResource(configResource);
        }
      }
    
      shouldLoadResource(stateResource: StateResource<T>, configResource: B): boolean {
        return isNotNull(configResource) && isLoadingRequired(stateResource);
      }
    
      loadResource(configResource: B): void {
        this.verifyGetLink(configResource);
        this.setStateResourceLoading();
        this.doLoadResource(configResource);
      }
    
      handleError(errorResponse: HttpErrorResponse): Observable<StateResource<HttpError>> {
        if (isUnprocessableEntity(errorResponse.status)) {
          return of(createErrorStateResource((<any>errorResponse) as ProblemDetail));
        }
        return throwError(() => errorResponse);
      }
    
      private verifyGetLink(configResource: B): void {
        throwErrorOn(
          !hasLink(configResource, this.config.getLinkRel),
          'No get link exists on configresource.',
        );
      }
    
      setStateResourceLoading(): void {
        this.stateResource.next({ ...createEmptyStateResource(true), reload: false });
      }
    
      doLoadResource(configResource: B): void {
        this.repository
          .getResource(getUrl(configResource, this.config.getLinkRel))
          .pipe()
          .subscribe((loadedResource: T) => this.updateStateResource(loadedResource));
      }
    
      updateStateResource(resource: T): void {
        this.stateResource.next(createStateResource(resource));
      }
    
      shouldFilter(stateResource: StateResource<Resource>): boolean {
        return (
          stateResource.reload || this.isInvalidResourceCombination(stateResource, this.configResource)
        );
      }
    
      isInvalidResourceCombination(
        stateResource: StateResource<Resource>,
        baseResource: Resource,
      ): boolean {
        return (
          !stateResource.loading &&
          ((!stateResource.loaded && isNotNull(baseResource)) ||
            (stateResource.loaded && isNull(baseResource)))
        );
      }
    
      public save(toSave: unknown): Observable<StateResource<T | HttpError>> {
        this.verifyEditLinkRel();
        return this.stateResource.asObservable().pipe(
          mergeMap((selectedResource: StateResource<T>) =>
            this.doSave(selectedResource.resource, toSave),
          ),
          map((loadedResource: T) => createStateResource(loadedResource)),
          catchError((errorResponse: HttpErrorResponse) => this.handleError(errorResponse)),
        );
      }
    
      private verifyEditLinkRel(): void {
        throwErrorOn(
          !this.hasLinkRel(this.config.editLinkRel),
          'No edit link exists on current stateresource.',
        );
      }
    
      private doSave(resource: T, toSave: unknown): Observable<T> {
        return <Observable<T>>this.repository.save({
          resource,
          linkRel: this.config.editLinkRel,
          toSave,
        });
      }
    
      public refresh(): void {
        this.stateResource.next({ ...this.stateResource.value, reload: true });
      }
    
      public canEdit(): boolean {
        return this.hasLinkRel(this.config.editLinkRel);
      }
    
      public canDelete(): boolean {
        return this.hasLinkRel(this.config.deleteLinkRel);
      }
    
      private hasLinkRel(linkRel: string): boolean {
        return hasLink(this.getStateResource(), linkRel);
      }
    
      private getStateResource(): T {
        return this.stateResource.value.resource;
      }
    
      public delete(): Observable<Resource> {
        this.verifyDeleteLinkRel();
        return this.repository.delete(this.getStateResource(), this.config.deleteLinkRel);
      }
    
      private verifyDeleteLinkRel(): void {
        throwErrorOn(
          !this.hasLinkRel(this.config.deleteLinkRel),
          'No delete link exists on current stateresource.',
        );
      }
    }