From ad4e8eeb8c5cab5aa657ad9a0c09ae9c00c67ea2 Mon Sep 17 00:00:00 2001
From: Art Lowel <art.lowel@atmire.com>
Date: Wed, 11 Dec 2019 17:18:08 +0100
Subject: [PATCH] refactored items, bundles and bitstreams, test builders

---
 .../item-collection-mapper.component.ts       |  12 +-
 .../collections/collections.component.html    |   4 +-
 .../collections/collections.component.ts      |  39 +++--
 .../full-file-section.component.html          |   6 +-
 .../full-file-section.component.ts            |  44 ++++--
 .../file-section/file-section.component.html  |   4 +-
 .../file-section/file-section.component.ts    |  13 +-
 .../publication/publication.component.html    |   2 +-
 .../item-types/shared/item.component.ts       |  14 ++
 .../core/cache/builders/bitstream-builder.ts  |  60 +++++++
 .../core/cache/builders/build-decorators.ts   |   5 +-
 .../builders/remote-data-build.service.ts     |  85 ++++++----
 .../models/normalized-bitstream.model.ts      |   7 +-
 .../cache/models/normalized-bundle.model.ts   |   4 +-
 .../models/normalized-collection.model.ts     |  11 +-
 .../cache/models/normalized-item.model.ts     |   8 +-
 .../data/base-response-parsing.service.ts     |  38 ++---
 src/app/core/data/bitstream-data.service.ts   | 147 ++++++++++++++++++
 .../data/bitstream-format-data.service.ts     |   6 +
 src/app/core/data/bundle-data.service.ts      |  53 +++++--
 src/app/core/data/collection-data.service.ts  |   6 +
 src/app/core/data/comcol-data.service.ts      |   4 +-
 src/app/core/data/data.service.ts             |  47 ++++--
 src/app/core/data/item-data.service.ts        |   5 +-
 src/app/core/data/relationship.service.ts     |   4 +-
 src/app/core/data/resource-policy.service.ts  |   2 +-
 src/app/core/metadata/metadata.service.ts     |  46 +++---
 src/app/core/shared/HALLink.model.ts          |   5 +
 src/app/core/shared/bitstream.model.ts        |  26 ++--
 src/app/core/shared/bundle.model.ts           |  33 +---
 src/app/core/shared/collection.model.ts       |  10 ++
 src/app/core/shared/item.model.ts             |  96 ++----------
 src/app/core/shared/operators.ts              |  82 ++++++++++
 ...-search-result-grid-element.component.html |   4 +-
 ...-search-result-grid-element.component.html |   4 +-
 ...-search-result-grid-element.component.html |   4 +-
 .../journal-issue.component.html              |   2 +-
 .../journal-volume.component.html             |   2 +-
 .../item-pages/journal/journal.component.html |   2 +-
 ...-search-result-grid-element.component.html |   4 +-
 ...-search-result-grid-element.component.html |   4 +-
 ...-search-result-grid-element.component.html |   4 +-
 .../org-unit/org-unit.component.html          |   2 +-
 .../item-pages/person/person.component.html   |   2 +-
 .../item-pages/project/project.component.html |   2 +-
 .../comcol-page-logo.component.html           |   2 +-
 .../item-detail-preview.component.ts          |  34 +++-
 .../grid-thumbnail.component.ts               |   4 +-
 ...-search-result-grid-element.component.html |   4 +-
 .../search-result-grid-element.component.ts   |  23 ++-
 src/app/thumbnail/thumbnail.component.ts      |  27 ++--
 .../publication/publication.component.html    |   2 +-
 .../journal-issue.component.html              |   2 +-
 .../journal-volume.component.html             |   2 +-
 .../item-pages/journal/journal.component.html |   2 +-
 .../org-unit/org-unit.component.html          |   2 +-
 .../item-pages/person/person.component.html   |   2 +-
 .../item-pages/project/project.component.html |   2 +-
 58 files changed, 739 insertions(+), 333 deletions(-)
 create mode 100644 src/app/core/cache/builders/bitstream-builder.ts
 create mode 100644 src/app/core/data/bitstream-data.service.ts
 create mode 100644 src/app/core/shared/HALLink.model.ts

diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts
index 5494d5ab5f..b22949d7b5 100644
--- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts
+++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts
@@ -1,12 +1,18 @@
 import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
 
 import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
+import { CollectionDataService } from '../../../core/data/collection-data.service';
 import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
 import { RemoteData } from '../../../core/data/remote-data';
 import { PaginatedList } from '../../../core/data/paginated-list';
 import { Collection } from '../../../core/shared/collection.model';
 import { Item } from '../../../core/shared/item.model';
-import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators';
+import {
+  getFirstSucceededRemoteDataPayload,
+  getRemoteDataPayload,
+  getSucceededRemoteData,
+  toDSpaceObjectListRD
+} from '../../../core/shared/operators';
 import { ActivatedRoute, Router } from '@angular/router';
 import { map, startWith, switchMap, take } from 'rxjs/operators';
 import { ItemDataService } from '../../../core/data/item-data.service';
@@ -81,6 +87,7 @@ export class ItemCollectionMapperComponent implements OnInit {
               private searchService: SearchService,
               private notificationsService: NotificationsService,
               private itemDataService: ItemDataService,
+              private collectionDataService: CollectionDataService,
               private translateService: TranslateService) {
   }
 
@@ -106,7 +113,8 @@ export class ItemCollectionMapperComponent implements OnInit {
     );
 
     const owningCollectionRD$ = this.itemRD$.pipe(
-      switchMap((itemRD: RemoteData<Item>) => itemRD.payload.owningCollection)
+      getFirstSucceededRemoteDataPayload(),
+      switchMap((item: Item) => this.collectionDataService.findOwningCollectionFor(item))
     );
     const itemCollectionsAndOptions$ = observableCombineLatest(
       this.itemCollectionsRD$,
diff --git a/src/app/+item-page/field-components/collections/collections.component.html b/src/app/+item-page/field-components/collections/collections.component.html
index 6e5f9a350c..e0f963b5bc 100644
--- a/src/app/+item-page/field-components/collections/collections.component.html
+++ b/src/app/+item-page/field-components/collections/collections.component.html
@@ -1,6 +1,6 @@
-<ds-metadata-field-wrapper *ngIf="hasSucceeded() | async" [label]="label | translate">
+<ds-metadata-field-wrapper *ngIf="(this.collectionsRD$ | async)?.hasSucceeded" [label]="label | translate">
     <div class="collections">
-        <a *ngFor="let collection of (collections | async); let last=last;" [routerLink]="['/collections', collection.id]">
+        <a *ngFor="let collection of (this.collectionsRD$ | async)?.payload?.page; let last=last;" [routerLink]="['/collections', collection.id]">
             <span>{{collection?.name}}</span><span *ngIf="!last" [innerHTML]="separator"></span>
         </a>
     </div>
diff --git a/src/app/+item-page/field-components/collections/collections.component.ts b/src/app/+item-page/field-components/collections/collections.component.ts
index b33c5fd41b..0d50fcad83 100644
--- a/src/app/+item-page/field-components/collections/collections.component.ts
+++ b/src/app/+item-page/field-components/collections/collections.component.ts
@@ -1,12 +1,13 @@
-
-import {map} from 'rxjs/operators';
 import { Component, Input, OnInit } from '@angular/core';
 import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { CollectionDataService } from '../../../core/data/collection-data.service';
+import { PaginatedList } from '../../../core/data/paginated-list';
+import { RemoteData } from '../../../core/data/remote-data';
 
 import { Collection } from '../../../core/shared/collection.model';
 import { Item } from '../../../core/shared/item.model';
-import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
-import { RemoteData } from '../../../core/data/remote-data';
+import { PageInfo } from '../../../core/shared/page-info.model';
 
 /**
  * This component renders the parent collections section of the item
@@ -25,9 +26,9 @@ export class CollectionsComponent implements OnInit {
 
   separator = '<br/>';
 
-  collections: Observable<Collection[]>;
+  collectionsRD$: Observable<RemoteData<PaginatedList<Collection>>>;
 
-  constructor(private rdbs: RemoteDataBuildService) {
+  constructor(private cds: CollectionDataService) {
 
   }
 
@@ -37,11 +38,25 @@ export class CollectionsComponent implements OnInit {
     // TODO: this should use parents, but the collections
     // for an Item aren't returned by the REST API yet,
     // only the owning collection
-    this.collections = this.item.owner.pipe(map((rd: RemoteData<Collection>) => [rd.payload]));
+    this.collectionsRD$ = this.cds.findOwningCollectionFor(this.item).pipe(
+      map((rd: RemoteData<Collection>) => {
+        if (rd.hasSucceeded) {
+          return new RemoteData(
+            false,
+            false,
+            true,
+            undefined,
+            new PaginatedList({
+              elementsPerPage: 10,
+              totalPages: 1,
+              currentPage: 1,
+              totalElements: 1
+            } as PageInfo, [rd.payload])
+          );
+        } else {
+          return rd as any;
+        }
+      })
+    );
   }
-
-  hasSucceeded() {
-    return this.item.owner.pipe(map((rd: RemoteData<Collection>) => rd.hasSucceeded));
-  }
-
 }
diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html
index a68993cd16..b8ab9bdb41 100644
--- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html
+++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html
@@ -1,7 +1,7 @@
 <ds-metadata-field-wrapper [label]="label | translate">
-    <div class="file-section row" *ngFor="let file of (bitstreamsObs | async); let last=last;">
+    <div class="file-section row" *ngFor="let file of (bitstreams$ | async); let last=last;">
         <div class="col-3">
-            <ds-thumbnail [thumbnail]="thumbnails.get(file.id) | async"></ds-thumbnail>
+            <ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail>
         </div>
         <div class="col-7">
             <dl class="row">
@@ -21,7 +21,7 @@
             </dl>
         </div>
         <div class="col-2">
-            <a [href]="file.content" [download]="file.name">
+            <a [href]="file._links.content.href" [download]="file.name">
                 {{"item.page.filesection.download" | translate}}
             </a>
         </div>
diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts b/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts
index 23d9ef05d0..0ce0c904a9 100644
--- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts
+++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts
@@ -1,10 +1,13 @@
+import { Component, Injector, Input, OnInit } from '@angular/core';
 import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
-import { Component, Input, OnInit } from '@angular/core';
+import { map, startWith } from 'rxjs/operators';
+import { getBitstreamBuilder } from '../../../../core/cache/builders/bitstream-builder';
+import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
 
 import { Bitstream } from '../../../../core/shared/bitstream.model';
 import { Item } from '../../../../core/shared/item.model';
+import { getFirstSucceededRemoteListPayload } from '../../../../core/shared/operators';
 import { FileSectionComponent } from '../../../simple/field-components/file-section/file-section.component';
-import { map } from 'rxjs/operators';
 
 /**
  * This component renders the file section of the item
@@ -22,27 +25,42 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
 
   label: string;
 
-  bitstreamsObs: Observable<Bitstream[]>;
+  bitstreams$: Observable<Bitstream[]>;
 
-  thumbnails: Map<string, Observable<Bitstream>> = new Map();
+  constructor(
+    bitstreamDataService: BitstreamDataService,
+    private parentInjector: Injector
+  ) {
+    super(bitstreamDataService);
+  }
 
   ngOnInit(): void {
     super.ngOnInit();
   }
 
   initialize(): void {
-    const originals = this.item.getFiles();
-    const licenses = this.item.getBitstreamsByBundleName('LICENSE');
-    this.bitstreamsObs = observableCombineLatest(originals, licenses).pipe(map(([o, l]) => [...o, ...l]));
-    this.bitstreamsObs.subscribe(
-      (files) =>
-        files.forEach(
+    // TODO pagination
+    const originals$ = this.bitstreamDataService.findAllByItemAndBundleName(this.item, 'ORIGINAL', { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
+      getFirstSucceededRemoteListPayload(),
+      startWith([])
+    );
+    const licenses$ = this.bitstreamDataService.findAllByItemAndBundleName(this.item, 'LICENSE', { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
+      getFirstSucceededRemoteListPayload(),
+      startWith([])
+    );
+    this.bitstreams$ = observableCombineLatest(originals$, licenses$).pipe(
+      map(([o, l]) => [...o, ...l]),
+      map((files: Bitstream[]) =>
+        files.map(
           (original) => {
-            const thumbnail: Observable<Bitstream> = this.item.getThumbnailForOriginal(original);
-            this.thumbnails.set(original.id, thumbnail);
+            return getBitstreamBuilder(this.parentInjector, original)
+              .loadThumbnail(this.item)
+              .loadBitstreamFormat()
+              .build();
           }
         )
-    )
+      )
+    );
   }
 
 }
diff --git a/src/app/+item-page/simple/field-components/file-section/file-section.component.html b/src/app/+item-page/simple/field-components/file-section/file-section.component.html
index 7063bac0be..6533322e03 100644
--- a/src/app/+item-page/simple/field-components/file-section/file-section.component.html
+++ b/src/app/+item-page/simple/field-components/file-section/file-section.component.html
@@ -1,7 +1,7 @@
-<ng-container *ngVar="(bitstreamsObs | async) as bitstreams">
+<ng-container *ngVar="(bitstreams$ | async) as bitstreams">
   <ds-metadata-field-wrapper *ngIf="bitstreams?.length > 0" [label]="label | translate">
     <div class="file-section">
-      <a *ngFor="let file of bitstreams; let last=last;" [href]="file?.content" [download]="file?.name">
+      <a *ngFor="let file of bitstreams; let last=last;" [href]="file?._links.content.href" [download]="file?.name">
         <span>{{file?.name}}</span>
         <span>({{(file?.sizeBytes) | dsFileSize }})</span>
         <span *ngIf="!last" innerHTML="{{separator}}"></span>
diff --git a/src/app/+item-page/simple/field-components/file-section/file-section.component.ts b/src/app/+item-page/simple/field-components/file-section/file-section.component.ts
index 8c40d123bf..2e09c1cd49 100644
--- a/src/app/+item-page/simple/field-components/file-section/file-section.component.ts
+++ b/src/app/+item-page/simple/field-components/file-section/file-section.component.ts
@@ -1,8 +1,10 @@
 import { Component, Input, OnInit } from '@angular/core';
 import { Observable } from 'rxjs';
+import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
 
 import { Bitstream } from '../../../../core/shared/bitstream.model';
 import { Item } from '../../../../core/shared/item.model';
+import { getFirstSucceededRemoteListPayload } from '../../../../core/shared/operators';
 
 /**
  * This component renders the file section of the item
@@ -20,14 +22,21 @@ export class FileSectionComponent implements OnInit {
 
   separator = '<br/>';
 
-  bitstreamsObs: Observable<Bitstream[]>;
+  bitstreams$: Observable<Bitstream[]>;
+
+  constructor(
+    protected bitstreamDataService: BitstreamDataService
+  ) {
+  }
 
   ngOnInit(): void {
     this.initialize();
   }
 
   initialize(): void {
-    this.bitstreamsObs = this.item.getFiles();
+    this.bitstreams$ = this.bitstreamDataService.findAllByItemAndBundleName(this.item, 'ORIGINAL').pipe(
+      getFirstSucceededRemoteListPayload()
+    );
   }
 
 }
diff --git a/src/app/+item-page/simple/item-types/publication/publication.component.html b/src/app/+item-page/simple/item-types/publication/publication.component.html
index c45e85668a..9eb704b9e9 100644
--- a/src/app/+item-page/simple/item-types/publication/publication.component.html
+++ b/src/app/+item-page/simple/item-types/publication/publication.component.html
@@ -4,7 +4,7 @@
 <div class="row">
   <div class="col-xs-12 col-md-4">
     <ds-metadata-field-wrapper>
-      <ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
+      <ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
     </ds-metadata-field-wrapper>
     <ds-item-page-file-section [item]="object"></ds-item-page-file-section>
     <ds-item-page-date-field [item]="object"></ds-item-page-date-field>
diff --git a/src/app/+item-page/simple/item-types/shared/item.component.ts b/src/app/+item-page/simple/item-types/shared/item.component.ts
index 64a96fdd52..abfcd24346 100644
--- a/src/app/+item-page/simple/item-types/shared/item.component.ts
+++ b/src/app/+item-page/simple/item-types/shared/item.component.ts
@@ -1,5 +1,9 @@
 import { Component, Input } from '@angular/core';
+import { Observable } from 'rxjs/internal/Observable';
+import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
+import { Bitstream } from '../../../../core/shared/bitstream.model';
 import { Item } from '../../../../core/shared/item.model';
+import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
 
 @Component({
   selector: 'ds-item',
@@ -10,4 +14,14 @@ import { Item } from '../../../../core/shared/item.model';
  */
 export class ItemComponent {
   @Input() object: Item;
+
+  constructor(protected bitstreamDataService: BitstreamDataService) {
+  }
+
+  // TODO refactor to return RemoteData, and thumbnail template to deal with loading
+  getThumbnail(): Observable<Bitstream> {
+    return this.bitstreamDataService.getThumbnailFor(this.object).pipe(
+      getFirstSucceededRemoteDataPayload()
+    );
+  }
 }
diff --git a/src/app/core/cache/builders/bitstream-builder.ts b/src/app/core/cache/builders/bitstream-builder.ts
new file mode 100644
index 0000000000..4e50992aea
--- /dev/null
+++ b/src/app/core/cache/builders/bitstream-builder.ts
@@ -0,0 +1,60 @@
+import { Injector } from '@angular/core';
+import { Observable } from 'rxjs/internal/Observable';
+import { BitstreamDataService } from '../../data/bitstream-data.service';
+import { BitstreamFormatDataService } from '../../data/bitstream-format-data.service';
+import { RemoteData } from '../../data/remote-data';
+import { BitstreamFormat } from '../../shared/bitstream-format.model';
+import { Bitstream } from '../../shared/bitstream.model';
+import { Item } from '../../shared/item.model';
+
+export const getBitstreamBuilder = (parentInjector: Injector, bitstream: Bitstream) => {
+  const injector = Injector.create({
+    providers:[
+      {
+        provide: BitstreamBuilder,
+        useClass: BitstreamBuilder,
+        deps:[
+          BitstreamDataService,
+          BitstreamFormatDataService,
+        ]
+      }
+    ],
+    parent: parentInjector
+  });
+  return injector.get(BitstreamBuilder).initWithBitstream(bitstream);
+};
+
+export class BitstreamBuilder {
+  private bitstream: Bitstream;
+  private thumbnail: Observable<RemoteData<Bitstream>>;
+  private format: Observable<RemoteData<BitstreamFormat>>;
+
+  constructor(
+    protected bitstreamDataService: BitstreamDataService,
+    protected bitstreamFormatDataService: BitstreamFormatDataService
+  ) {
+  }
+
+  initWithBitstream(bitstream: Bitstream): BitstreamBuilder {
+    this.bitstream = bitstream;
+    return this;
+  }
+
+  loadThumbnail(item: Item): BitstreamBuilder {
+    this.thumbnail = this.bitstreamDataService.getMatchingThumbnail(item, this.bitstream);
+    return this;
+  }
+
+  loadBitstreamFormat(): BitstreamBuilder {
+    this.format = this.bitstreamFormatDataService.findByBitstream(this.bitstream);
+    return this;
+  }
+
+  build(): Bitstream {
+    const bitstream = this.bitstream;
+    bitstream.thumbnail = this.thumbnail;
+    bitstream.format = this.format;
+    return bitstream;
+  }
+
+}
diff --git a/src/app/core/cache/builders/build-decorators.ts b/src/app/core/cache/builders/build-decorators.ts
index 0bfb5f0321..19fb7c881f 100644
--- a/src/app/core/cache/builders/build-decorators.ts
+++ b/src/app/core/cache/builders/build-decorators.ts
@@ -53,7 +53,7 @@ export function getMapsToType(type: string | ResourceType) {
   return typeMap.get(type);
 }
 
-export function relationship<T extends CacheableObject>(value: GenericConstructor<T>, isList: boolean = false): any {
+export function relationship<T extends CacheableObject>(value: GenericConstructor<T>, isList: boolean = false, shouldAutoResolve: boolean = true): any {
   return function r(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
     if (!target || !propertyKey) {
       return;
@@ -66,7 +66,8 @@ export function relationship<T extends CacheableObject>(value: GenericConstructo
     relationshipMap.set(target.constructor, metaDataList);
     return Reflect.metadata(relationshipKey, {
       resourceType: (value as any).type.value,
-      isList
+      isList,
+      shouldAutoResolve
     }).apply(this, arguments);
   };
 }
diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts
index 403a0273bc..5df3f20ca5 100644
--- a/src/app/core/cache/builders/remote-data-build.service.ts
+++ b/src/app/core/cache/builders/remote-data-build.service.ts
@@ -1,9 +1,21 @@
 import { Injectable } from '@angular/core';
 
-import { combineLatest as observableCombineLatest, Observable, of as observableOf, race as observableRace } from 'rxjs';
+import {
+  combineLatest as observableCombineLatest,
+  Observable,
+  of as observableOf,
+  race as observableRace
+} from 'rxjs';
 import { distinctUntilChanged, flatMap, map, startWith, switchMap, tap } from 'rxjs/operators';
 
-import { hasValue, hasValueOperator, isEmpty, isNotEmpty, isNotUndefined } from '../../../shared/empty.util';
+import {
+  hasNoValue,
+  hasValue,
+  hasValueOperator,
+  isEmpty,
+  isNotEmpty,
+  isNotUndefined
+} from '../../../shared/empty.util';
 import { PaginatedList } from '../../data/paginated-list';
 import { RemoteData } from '../../data/remote-data';
 import { RemoteDataError } from '../../data/remote-data-error';
@@ -157,46 +169,55 @@ export class RemoteDataBuildService {
     relationships.forEach((relationship: string) => {
       let result;
       if (hasValue(normalized[relationship])) {
-        const { resourceType, isList } = getRelationMetadata(normalized, relationship);
+        const { resourceType, isList, shouldAutoResolve } = getRelationMetadata(normalized, relationship);
         const objectList = normalized[relationship].page || normalized[relationship];
-        if (typeof objectList !== 'string') {
-          objectList.forEach((href: string) => {
-            const request = new GetRequest(this.requestService.generateRequestId(), href);
+        if (shouldAutoResolve) {
+          if (typeof objectList !== 'string') {
+            objectList.forEach((href: string) => {
+              const request = new GetRequest(this.requestService.generateRequestId(), href);
+              if (!this.requestService.isCachedOrPending(request)) {
+                this.requestService.configure(request)
+              }
+            });
+
+            const rdArr = [];
+            objectList.forEach((href: string) => {
+              rdArr.push(this.buildSingle(href));
+            });
+
+            if (isList) {
+              result = this.aggregate(rdArr);
+            } else if (rdArr.length === 1) {
+              result = rdArr[0];
+            }
+          } else {
+            const request = new GetRequest(this.requestService.generateRequestId(), objectList);
             if (!this.requestService.isCachedOrPending(request)) {
               this.requestService.configure(request)
             }
-          });
 
-          const rdArr = [];
-          objectList.forEach((href: string) => {
-            rdArr.push(this.buildSingle(href));
-          });
-
-          if (isList) {
-            result = this.aggregate(rdArr);
-          } else if (rdArr.length === 1) {
-            result = rdArr[0];
-          }
-        } else {
-          const request = new GetRequest(this.requestService.generateRequestId(), objectList);
-          if (!this.requestService.isCachedOrPending(request)) {
-            this.requestService.configure(request)
+            // The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams)
+            // in that case only 1 href will be stored in the normalized obj (so the isArray above fails),
+            // but it should still be built as a list
+            if (isList) {
+              result = this.buildList(objectList);
+            } else {
+              result = this.buildSingle(objectList);
+            }
           }
 
-          // The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams)
-          // in that case only 1 href will be stored in the normalized obj (so the isArray above fails),
-          // but it should still be built as a list
-          if (isList) {
-            result = this.buildList(objectList);
+          if (hasValue(normalized[relationship].page)) {
+            links[relationship] = this.toPaginatedList(result, normalized[relationship].pageInfo);
           } else {
-            result = this.buildSingle(objectList);
+            links[relationship] = result;
           }
-        }
-
-        if (hasValue(normalized[relationship].page)) {
-          links[relationship] = this.toPaginatedList(result, normalized[relationship].pageInfo);
         } else {
-          links[relationship] = result;
+          if (hasNoValue(links._links)) {
+            links._links = {};
+          }
+          links._links[relationship] = {
+            href: objectList
+          };
         }
       }
     });
diff --git a/src/app/core/cache/models/normalized-bitstream.model.ts b/src/app/core/cache/models/normalized-bitstream.model.ts
index a9e389fd41..028c1a24de 100644
--- a/src/app/core/cache/models/normalized-bitstream.model.ts
+++ b/src/app/core/cache/models/normalized-bitstream.model.ts
@@ -22,13 +22,14 @@ export class NormalizedBitstream extends NormalizedDSpaceObject<Bitstream> {
    * The relative path to this Bitstream's file
    */
   @autoserialize
+  @relationship(Bitstream, false, false)
   content: string;
 
   /**
    * The format of this Bitstream
    */
   @autoserialize
-  @relationship(BitstreamFormat, false)
+  @relationship(BitstreamFormat, false, false)
   format: string;
 
   /**
@@ -41,14 +42,14 @@ export class NormalizedBitstream extends NormalizedDSpaceObject<Bitstream> {
    * An array of Bundles that are direct parents of this Bitstream
    */
   @autoserialize
-  @relationship(Item, true)
+  @relationship(Item, true, false)
   parents: string[];
 
   /**
    * The Bundle that owns this Bitstream
    */
   @autoserialize
-  @relationship(Item, false)
+  @relationship(Item, false, false)
   owner: string;
 
   /**
diff --git a/src/app/core/cache/models/normalized-bundle.model.ts b/src/app/core/cache/models/normalized-bundle.model.ts
index 9582643efb..0eff125c87 100644
--- a/src/app/core/cache/models/normalized-bundle.model.ts
+++ b/src/app/core/cache/models/normalized-bundle.model.ts
@@ -22,7 +22,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject<Bundle> {
    * The primary bitstream of this Bundle
    */
   @autoserialize
-  @relationship(Bitstream, false)
+  @relationship(Bitstream, false, false)
   primaryBitstream: string;
 
   /**
@@ -39,7 +39,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject<Bundle> {
    * List of Bitstreams that are part of this Bundle
    */
   @autoserialize
-  @relationship(Bitstream, true)
+  @relationship(Bitstream, true, false)
   bitstreams: string[];
 
 }
diff --git a/src/app/core/cache/models/normalized-collection.model.ts b/src/app/core/cache/models/normalized-collection.model.ts
index 9b3419675a..f79ed024fc 100644
--- a/src/app/core/cache/models/normalized-collection.model.ts
+++ b/src/app/core/cache/models/normalized-collection.model.ts
@@ -30,42 +30,41 @@ export class NormalizedCollection extends NormalizedDSpaceObject<Collection> {
    * The Bitstream that represents the license of this Collection
    */
   @autoserialize
-  @relationship(License, false)
   license: string;
 
   /**
    * The Bitstream that represents the default Access Conditions of this Collection
    */
   @autoserialize
-  @relationship(ResourcePolicy, false)
+  @relationship(ResourcePolicy, false, false)
   defaultAccessConditions: string;
 
   /**
    * The Bitstream that represents the logo of this Collection
    */
   @deserialize
-  @relationship(Bitstream, false)
+  @relationship(Bitstream, false, false)
   logo: string;
 
   /**
    * An array of Communities that are direct parents of this Collection
    */
   @deserialize
-  @relationship(Community, true)
+  @relationship(Community, true, false)
   parents: string[];
 
   /**
    * The Community that owns this Collection
    */
   @deserialize
-  @relationship(Community, false)
+  @relationship(Community, false, false)
   owner: string;
 
   /**
    * List of Items that are part of (not necessarily owned by) this Collection
    */
   @deserialize
-  @relationship(Item, true)
+  @relationship(Item, true, false)
   items: string[];
 
 }
diff --git a/src/app/core/cache/models/normalized-item.model.ts b/src/app/core/cache/models/normalized-item.model.ts
index 9b7edf70c0..7e326d6c1d 100644
--- a/src/app/core/cache/models/normalized-item.model.ts
+++ b/src/app/core/cache/models/normalized-item.model.ts
@@ -48,25 +48,25 @@ export class NormalizedItem extends NormalizedDSpaceObject<Item> {
    * An array of Collections that are direct parents of this Item
    */
   @deserialize
-  @relationship(Collection, true)
+  @relationship(Collection, true, false)
   parents: string[];
 
   /**
    * The Collection that owns this Item
    */
   @deserialize
-  @relationship(Collection, false)
+  @relationship(Collection, false, false)
   owningCollection: string;
 
   /**
    * List of Bitstreams that are owned by this Item
    */
   @deserialize
-  @relationship(Bundle, true)
+  @relationship(Bundle, true, false)
   bundles: string[];
 
   @deserialize
-  @relationship(Relationship, true)
+  @relationship(Relationship, true, false)
   relationships: string[];
 
 }
diff --git a/src/app/core/data/base-response-parsing.service.ts b/src/app/core/data/base-response-parsing.service.ts
index ea2d71faa7..d1bc6e9e10 100644
--- a/src/app/core/data/base-response-parsing.service.ts
+++ b/src/app/core/data/base-response-parsing.service.ts
@@ -27,24 +27,26 @@ export abstract class BaseResponseParsingService {
         return this.processArray(data, request);
       } else if (isRestDataObject(data)) {
         const object = this.deserialize(data);
-        if (isNotEmpty(data._embedded)) {
-          Object
-            .keys(data._embedded)
-            .filter((property) => data._embedded.hasOwnProperty(property))
-            .forEach((property) => {
-              const parsedObj = this.process<ObjectDomain>(data._embedded[property], request);
-              if (isNotEmpty(parsedObj)) {
-                if (isRestPaginatedList(data._embedded[property])) {
-                  object[property] = parsedObj;
-                  object[property].page = parsedObj.page.map((obj) => this.retrieveObjectOrUrl(obj));
-                } else if (isRestDataObject(data._embedded[property])) {
-                  object[property] = this.retrieveObjectOrUrl(parsedObj);
-                } else if (Array.isArray(parsedObj)) {
-                  object[property] = parsedObj.map((obj) => this.retrieveObjectOrUrl(obj))
-                }
-              }
-            });
-        }
+
+        // TODO remove
+        // if (isNotEmpty(data._embedded)) {
+        //   Object
+        //     .keys(data._embedded)
+        //     .filter((property) => data._embedded.hasOwnProperty(property))
+        //     .forEach((property) => {
+        //       const parsedObj = this.process<ObjectDomain>(data._embedded[property], request);
+        //       if (isNotEmpty(parsedObj)) {
+        //         if (isRestPaginatedList(data._embedded[property])) {
+        //           object[property] = parsedObj;
+        //           object[property].page = parsedObj.page.map((obj) => this.retrieveObjectOrUrl(obj));
+        //         } else if (isRestDataObject(data._embedded[property])) {
+        //           object[property] = this.retrieveObjectOrUrl(parsedObj);
+        //         } else if (Array.isArray(parsedObj)) {
+        //           object[property] = parsedObj.map((obj) => this.retrieveObjectOrUrl(obj))
+        //         }
+        //       }
+        //     });
+        // }
 
         this.cache(object, request);
         return object;
diff --git a/src/app/core/data/bitstream-data.service.ts b/src/app/core/data/bitstream-data.service.ts
new file mode 100644
index 0000000000..5664e0a442
--- /dev/null
+++ b/src/app/core/data/bitstream-data.service.ts
@@ -0,0 +1,147 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Store } from '@ngrx/store';
+import { Observable } from 'rxjs/internal/Observable';
+import { map, switchMap } from 'rxjs/operators';
+import { hasNoValue, hasValue } from '../../shared/empty.util';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
+import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { CoreState } from '../core.reducers';
+import { Bitstream } from '../shared/bitstream.model';
+import { Bundle } from '../shared/bundle.model';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
+import { Item } from '../shared/item.model';
+import { BundleDataService } from './bundle-data.service';
+import { CommunityDataService } from './community-data.service';
+import { DataService } from './data.service';
+import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
+import { PaginatedList } from './paginated-list';
+import { RemoteData } from './remote-data';
+import { RemoteDataError } from './remote-data-error';
+import { FindListOptions } from './request.models';
+import { RequestService } from './request.service';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class BitstreamDataService extends DataService<Bitstream> {
+
+  protected linkPath = 'bitstreams';
+
+  constructor(
+    protected requestService: RequestService,
+    protected rdbService: RemoteDataBuildService,
+    protected dataBuildService: NormalizedObjectBuildService,
+    protected store: Store<CoreState>,
+    protected cds: CommunityDataService,
+    protected objectCache: ObjectCacheService,
+    protected halService: HALEndpointService,
+    protected notificationsService: NotificationsService,
+    protected http: HttpClient,
+    protected comparator: DSOChangeAnalyzer<Bitstream>,
+    protected bundleService: BundleDataService,
+  ) {
+    super();
+  }
+
+  getBrowseEndpoint(options: FindListOptions, linkPath?: string): Observable<string> {
+    // TODO needed? if not, perhaps remove it from datasevice?
+    return undefined;
+  }
+
+  /**
+   * Retrieves the bitstreams in a given bundle
+   *
+   * @param bundle the bundle to retrieve bitstreams from
+   * @param options options for the find all request
+   */
+  findAllByBundle(bundle: Bundle, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bitstream>>> {
+    return this.findAllByHref(bundle._links.bitstreams.href, options);
+  }
+
+  /**
+   * Retrieves the thumbnail for the given item
+   * @returns {Observable<RemoteData<Bitstream>>} the first bitstream in the THUMBNAIL bundle
+   */
+  // TODO should be implemented rest side. Item should get a thumbnail link
+  public getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
+    return this.bundleService.findByItemAndName(item, 'THUMBNAIL').pipe(
+      switchMap((bundleRD: RemoteData<Bundle>) => {
+        if (hasValue(bundleRD.payload)) {
+          return this.findAllByBundle(bundleRD.payload, { elementsPerPage: 1 }).pipe(
+            map((bitstreamRD: RemoteData<PaginatedList<Bitstream>>) => {
+              if (hasValue(bitstreamRD.payload) && hasValue(bitstreamRD.payload.page)) {
+                return new RemoteData(
+                  false,
+                  false,
+                  true,
+                  undefined,
+                  bitstreamRD.payload.page[0]
+                );
+              } else {
+                return bitstreamRD as any;
+              }
+            })
+          );
+        } else {
+          return [bundleRD as any];
+        }
+      })
+    );
+  }
+
+  // TODO should be implemented rest side
+  public getMatchingThumbnail(item: Item, bitstreamInOriginal: Bitstream): Observable<RemoteData<Bitstream>> {
+    return this.bundleService.findByItemAndName(item, 'THUMBNAIL').pipe(
+      switchMap((bundleRD: RemoteData<Bundle>) => {
+        if (hasValue(bundleRD.payload)) {
+          return this.findAllByBundle(bundleRD.payload, { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
+            map((bitstreamRD: RemoteData<PaginatedList<Bitstream>>) => {
+              if (hasValue(bitstreamRD.payload) && hasValue(bitstreamRD.payload.page)) {
+                const matchingThumbnail = bitstreamRD.payload.page.find((thumbnail: Bitstream) =>
+                  thumbnail.name.startsWith(bitstreamInOriginal.name)
+                );
+                if (hasValue(matchingThumbnail)) {
+                  return new RemoteData(
+                    false,
+                    false,
+                    true,
+                    undefined,
+                    matchingThumbnail
+                  );
+                } else {
+                  return new RemoteData(
+                    false,
+                    false,
+                    false,
+                    new RemoteDataError(404, '404', 'No matching thumbnail found'),
+                    undefined
+                  );
+                }
+              } else {
+                return bitstreamRD as any;
+              }
+            })
+          );
+        } else {
+          return [bundleRD as any];
+        }
+      })
+    );
+  }
+
+  public findAllByItemAndBundleName(item: Item, bundleName: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bitstream>>> {
+    return this.bundleService.findByItemAndName(item, bundleName).pipe(
+      switchMap((bundleRD: RemoteData<Bundle>) => {
+        if (hasValue(bundleRD.payload)) {
+          return this.findAllByBundle(bundleRD.payload, options);
+        } else {
+          return [bundleRD as any];
+        }
+      })
+    );
+  }
+
+}
diff --git a/src/app/core/data/bitstream-format-data.service.ts b/src/app/core/data/bitstream-format-data.service.ts
index c30330a0a3..c707c42075 100644
--- a/src/app/core/data/bitstream-format-data.service.ts
+++ b/src/app/core/data/bitstream-format-data.service.ts
@@ -1,6 +1,8 @@
 import { Injectable } from '@angular/core';
+import { Bitstream } from '../shared/bitstream.model';
 import { DataService } from './data.service';
 import { BitstreamFormat } from '../shared/bitstream-format.model';
+import { RemoteData } from './remote-data';
 import { RequestService } from './request.service';
 import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
 import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
@@ -183,4 +185,8 @@ export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
       map((request: RequestEntry) => request.response.isSuccessful)
     );
   }
+
+  findByBitstream(bitstream: Bitstream): Observable<RemoteData<BitstreamFormat>> {
+    return this.findByHref(bitstream._links.format.href);
+  }
 }
diff --git a/src/app/core/data/bundle-data.service.ts b/src/app/core/data/bundle-data.service.ts
index 280f727aad..f6f24a7d7f 100644
--- a/src/app/core/data/bundle-data.service.ts
+++ b/src/app/core/data/bundle-data.service.ts
@@ -1,23 +1,31 @@
+import { HttpClient } from '@angular/common/http';
 import { Injectable } from '@angular/core';
-import { DataService } from './data.service';
-import { Bundle } from '../shared/bundle.model';
-import { RequestService } from './request.service';
-import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
-import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
 import { Store } from '@ngrx/store';
-import { CoreState } from '../core.reducers';
+import { Observable } from 'rxjs/internal/Observable';
+import { map } from 'rxjs/operators';
+import { hasValue } from '../../shared/empty.util';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
+import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
 import { ObjectCacheService } from '../cache/object-cache.service';
+import { CoreState } from '../core.reducers';
+import { Bundle } from '../shared/bundle.model';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
-import { NotificationsService } from '../../shared/notifications/notifications.service';
-import { HttpClient } from '@angular/common/http';
+import { Item } from '../shared/item.model';
+import { BitstreamDataService } from './bitstream-data.service';
+import { DataService } from './data.service';
 import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
+import { PaginatedList } from './paginated-list';
+import { RemoteData } from './remote-data';
 import { FindListOptions } from './request.models';
-import { Observable } from 'rxjs/internal/Observable';
+import { RequestService } from './request.service';
 
 /**
  * A service responsible for fetching/sending data from/to the REST API on the bundles endpoint
  */
-@Injectable()
+@Injectable(
+  {providedIn: 'root'}
+)
 export class BundleDataService extends DataService<Bundle> {
   protected linkPath = 'bundles';
   protected forceBypassCache = false;
@@ -43,4 +51,29 @@ export class BundleDataService extends DataService<Bundle> {
   getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> {
     return this.halService.getEndpoint(this.linkPath);
   }
+
+  findAllByItem(item: Item, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bundle>>> {
+    return this.findAllByHref(item._links.bundles.href, options);
+  }
+
+  // TODO should be implemented rest side
+  findByItemAndName(item: Item, bundleName: string): Observable<RemoteData<Bundle>> {
+    return this.findAllByItem(item, { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
+      map((rd: RemoteData<PaginatedList<Bundle>>) => {
+        if (hasValue(rd.payload) && hasValue(rd.payload.page)) {
+          const matchingBundle = rd.payload.page.find((bundle: Bundle) =>
+            bundle.name === bundleName);
+          return new RemoteData(
+            false,
+            false,
+            true,
+            undefined,
+            matchingBundle
+          );
+        } else {
+          return rd as any;
+        }
+      }),
+    );
+  }
 }
diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts
index ed05c99e27..e6fb823c96 100644
--- a/src/app/core/data/collection-data.service.ts
+++ b/src/app/core/data/collection-data.service.ts
@@ -7,6 +7,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
 import { ObjectCacheService } from '../cache/object-cache.service';
 import { CoreState } from '../core.reducers';
 import { Collection } from '../shared/collection.model';
+import { Item } from '../shared/item.model';
 import { ComColDataService } from './comcol-data.service';
 import { CommunityDataService } from './community-data.service';
 import { RequestService } from './request.service';
@@ -240,4 +241,9 @@ export class CollectionDataService extends ComColDataService<Collection> {
         this.halService.getEndpoint('collections', `${communityEndpointHref}/${parentUUID}`)),
     );
   }
+
+  findOwningCollectionFor(item: Item): Observable<RemoteData<Collection>> {
+    return this.findByHref(item._links.owningCollection.href);
+  }
+
 }
diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts
index 2ce0362a4e..915de4862f 100644
--- a/src/app/core/data/comcol-data.service.ts
+++ b/src/app/core/data/comcol-data.service.ts
@@ -81,7 +81,9 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
   protected abstract getFindByParentHref(parentUUID: string): Observable<string>;
 
   public findByParent(parentUUID: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
-    const href$ = this.buildHrefFromFindOptions(this.getFindByParentHref(parentUUID), [], options);
+    const href$ = this.getFindByParentHref(parentUUID).pipe(
+      map((href: string) => this.buildHrefFromFindOptions(href, options))
+    );
     return this.findList(href$, options);
   }
 
diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts
index 82fdb82008..9a214ddd89 100644
--- a/src/app/core/data/data.service.ts
+++ b/src/app/core/data/data.service.ts
@@ -76,12 +76,12 @@ export abstract class DataService<T extends CacheableObject> {
    *    Return an observable that emits created HREF
    */
   protected getFindAllHref(options: FindListOptions = {}, linkPath?: string): Observable<string> {
-    let result: Observable<string>;
+    let result$: Observable<string>;
     const args = [];
 
-    result = this.getBrowseEndpoint(options, linkPath).pipe(distinctUntilChanged());
+    result$ = this.getBrowseEndpoint(options, linkPath).pipe(distinctUntilChanged());
 
-    return this.buildHrefFromFindOptions(result, args, options);
+    return result$.pipe(map((result: string) => this.buildHrefFromFindOptions(result, options, args)));
   }
 
   /**
@@ -93,10 +93,10 @@ export abstract class DataService<T extends CacheableObject> {
    *    Return an observable that emits created HREF
    */
   protected getSearchByHref(searchMethod: string, options: FindListOptions = {}): Observable<string> {
-    let result: Observable<string>;
+    let result$: Observable<string>;
     const args = [];
 
-    result = this.getSearchEndpoint(searchMethod);
+    result$ = this.getSearchEndpoint(searchMethod);
 
     if (hasValue(options.searchParams)) {
       options.searchParams.forEach((param: SearchParam) => {
@@ -104,37 +104,39 @@ export abstract class DataService<T extends CacheableObject> {
       })
     }
 
-    return this.buildHrefFromFindOptions(result, args, options);
+    return result$.pipe(map((result: string) => this.buildHrefFromFindOptions(result, options, args)));
   }
 
   /**
    * Turn an options object into a query string and combine it with the given HREF
    *
-   * @param href$ The HREF to which the query string should be appended
-   * @param args Array with additional params to combine with query string
+   * @param href The HREF to which the query string should be appended
    * @param options The [[FindListOptions]] object
+   * @param extraArgs Array with additional params to combine with query string
    * @return {Observable<string>}
    *    Return an observable that emits created HREF
    */
-  protected buildHrefFromFindOptions(href$: Observable<string>, args: string[], options: FindListOptions): Observable<string> {
+  protected buildHrefFromFindOptions(href: string, options: FindListOptions, extraArgs: string[] = []): string {
+
+    let args = [...extraArgs];
 
     if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
       /* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
-      args.push(`page=${options.currentPage - 1}`);
+      args = [...args, `page=${options.currentPage - 1}`];
     }
     if (hasValue(options.elementsPerPage)) {
-      args.push(`size=${options.elementsPerPage}`);
+      args = [...args, `size=${options.elementsPerPage}`];
     }
     if (hasValue(options.sort)) {
-      args.push(`sort=${options.sort.field},${options.sort.direction}`);
+      args = [...args, `sort=${options.sort.field},${options.sort.direction}`];
     }
     if (hasValue(options.startsWith)) {
-      args.push(`startsWith=${options.startsWith}`);
+      args = [...args, `startsWith=${options.startsWith}`];
     }
     if (isNotEmpty(args)) {
-      return href$.pipe(map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString()));
+      return new URLCombiner(href, `?${args.join('&')}`).toString();
     } else {
-      return href$;
+      return href;
     }
   }
 
@@ -183,8 +185,9 @@ export abstract class DataService<T extends CacheableObject> {
     return this.rdbService.buildSingle<T>(hrefObs);
   }
 
-  findByHref(href: string, options?: HttpOptions): Observable<RemoteData<T>> {
-    const request = new GetRequest(this.requestService.generateRequestId(), href, null, options);
+  findByHref(href: string, findListOptions: FindListOptions = {}, httpOptions?: HttpOptions): Observable<RemoteData<T>> {
+    const requestHref = this.buildHrefFromFindOptions(href, findListOptions, []);
+    const request = new GetRequest(this.requestService.generateRequestId(), requestHref, null, httpOptions);
     if (hasValue(this.responseMsToLive)) {
       request.responseMsToLive = this.responseMsToLive;
     }
@@ -192,6 +195,16 @@ export abstract class DataService<T extends CacheableObject> {
     return this.rdbService.buildSingle<T>(href);
   }
 
+  findAllByHref(href: string, findListOptions: FindListOptions = {}, httpOptions?: HttpOptions): Observable<RemoteData<PaginatedList<T>>> {
+    const requestHref = this.buildHrefFromFindOptions(href, findListOptions, []);
+    const request = new GetRequest(this.requestService.generateRequestId(), requestHref, null, httpOptions);
+    if (hasValue(this.responseMsToLive)) {
+      request.responseMsToLive = this.responseMsToLive;
+    }
+    this.requestService.configure(request);
+    return this.rdbService.buildList<T>(requestHref);
+  }
+
   /**
    * Return object search endpoint by given search method
    *
diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts
index cd7e70dc32..4ac6cac5cc 100644
--- a/src/app/core/data/item-data.service.ts
+++ b/src/app/core/data/item-data.service.ts
@@ -29,7 +29,7 @@ import {
   configureRequest,
   filterSuccessfulResponses,
   getRequestFromRequestHref,
-  getResponseFromEntry
+  getResponseFromEntry, getSucceededRemoteData
 } from '../shared/operators';
 import { RequestEntry } from './request.reducer';
 import { GenericSuccessResponse, RestResponse } from '../cache/response.models';
@@ -53,7 +53,8 @@ export class ItemDataService extends DataService<Item> {
     protected halService: HALEndpointService,
     protected notificationsService: NotificationsService,
     protected http: HttpClient,
-    protected comparator: DSOChangeAnalyzer<Item>) {
+    protected comparator: DSOChangeAnalyzer<Item>,
+    ) {
     super();
   }
 
diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts
index 0448c18ec6..20e3628de2 100644
--- a/src/app/core/data/relationship.service.ts
+++ b/src/app/core/data/relationship.service.ts
@@ -178,11 +178,11 @@ export class RelationshipService extends DataService<Relationship> {
   }
 
   /**
-   * Get an item its relationships in the form of an array
+   * Get an item's relationships in the form of an array
    * @param item
    */
   getItemRelationshipsArray(item: Item): Observable<Relationship[]> {
-    return item.relationships.pipe(
+    return this.findAllByHref(item._links.relationships.href).pipe(
       getSucceededRemoteData(),
       getRemoteDataPayload(),
       map((rels: PaginatedList<Relationship>) => rels.page),
diff --git a/src/app/core/data/resource-policy.service.ts b/src/app/core/data/resource-policy.service.ts
index 017e5cf5ee..79530dc7b1 100644
--- a/src/app/core/data/resource-policy.service.ts
+++ b/src/app/core/data/resource-policy.service.ts
@@ -61,6 +61,6 @@ export class ResourcePolicyService {
   }
 
   findByHref(href: string, options?: HttpOptions): Observable<RemoteData<ResourcePolicy>> {
-    return this.dataService.findByHref(href, options);
+    return this.dataService.findByHref(href, {}, options);
   }
 }
diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts
index 2b1cf4ffc1..06842638b7 100644
--- a/src/app/core/metadata/metadata.service.ts
+++ b/src/app/core/metadata/metadata.service.ts
@@ -1,29 +1,28 @@
-import {
-  catchError,
-  distinctUntilKeyChanged,
-  filter,
-  first,
-  map,
-  take
-} from 'rxjs/operators';
 import { Inject, Injectable } from '@angular/core';
-import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
 
 import { Meta, MetaDefinition, Title } from '@angular/platform-browser';
+import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
 
 import { TranslateService } from '@ngx-translate/core';
 
 import { BehaviorSubject, Observable } from 'rxjs';
+import { catchError, distinctUntilKeyChanged, filter, first, map, take } from 'rxjs/operators';
+
+import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
+import { hasValue, isNotEmpty } from '../../shared/empty.util';
+import { CacheableObject } from '../cache/object-cache.reducer';
+import { BitstreamDataService } from '../data/bitstream-data.service';
+import { BitstreamFormatDataService } from '../data/bitstream-format-data.service';
 
 import { RemoteData } from '../data/remote-data';
+import { BitstreamFormat } from '../shared/bitstream-format.model';
 import { Bitstream } from '../shared/bitstream.model';
-import { CacheableObject } from '../cache/object-cache.reducer';
 import { DSpaceObject } from '../shared/dspace-object.model';
 import { Item } from '../shared/item.model';
-
-import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
-import { BitstreamFormat } from '../shared/bitstream-format.model';
-import { hasValue, isNotEmpty } from '../../shared/empty.util';
+import {
+  getFirstSucceededRemoteDataPayload,
+  getFirstSucceededRemoteListPayload
+} from '../shared/operators';
 
 @Injectable()
 export class MetadataService {
@@ -39,6 +38,8 @@ export class MetadataService {
     private translate: TranslateService,
     private meta: Meta,
     private title: Title,
+    private bitstreamDataService: BitstreamDataService,
+    private bitstreamFormatDataService: BitstreamFormatDataService,
     @Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig
   ) {
     // TODO: determine what open graph meta tags are needed and whether
@@ -266,8 +267,9 @@ export class MetadataService {
   private setCitationPdfUrlTag(): void {
     if (this.currentObject.value instanceof Item) {
       const item = this.currentObject.value as Item;
-      item.getFiles()
+      this.bitstreamDataService.findAllByItemAndBundleName(item, 'ORIGINAL')
         .pipe(
+          getFirstSucceededRemoteListPayload(),
           first((files) => isNotEmpty(files)),
           catchError((error) => {
             console.debug(error.message);
@@ -275,17 +277,11 @@ export class MetadataService {
           }))
         .subscribe((bitstreams: Bitstream[]) => {
           for (const bitstream of bitstreams) {
-            bitstream.format.pipe(
-              first(),
-              catchError((error: Error) => {
-                console.debug(error.message);
-                return []
-              }),
-              map((rd: RemoteData<BitstreamFormat>) => rd.payload),
-              filter((format: BitstreamFormat) => hasValue(format)))
-              .subscribe((format: BitstreamFormat) => {
+            this.bitstreamFormatDataService.findByBitstream(bitstream).pipe(
+                getFirstSucceededRemoteDataPayload()
+              ).subscribe((format: BitstreamFormat) => {
                 if (format.mimetype === 'application/pdf') {
-                  this.addMetaTag('citation_pdf_url', bitstream.content);
+                  this.addMetaTag('citation_pdf_url', bitstream._links.content.href);
                 }
               });
           }
diff --git a/src/app/core/shared/HALLink.model.ts b/src/app/core/shared/HALLink.model.ts
new file mode 100644
index 0000000000..32c4c7c99f
--- /dev/null
+++ b/src/app/core/shared/HALLink.model.ts
@@ -0,0 +1,5 @@
+export class HALLink {
+  href: string;
+  name?: string;
+  templated?: boolean
+}
diff --git a/src/app/core/shared/bitstream.model.ts b/src/app/core/shared/bitstream.model.ts
index 887f7d0843..9f2eb15e52 100644
--- a/src/app/core/shared/bitstream.model.ts
+++ b/src/app/core/shared/bitstream.model.ts
@@ -1,8 +1,8 @@
-import { DSpaceObject } from './dspace-object.model';
+import { Observable } from 'rxjs';
 import { RemoteData } from '../data/remote-data';
-import { Item } from './item.model';
 import { BitstreamFormat } from './bitstream-format.model';
-import { Observable } from 'rxjs';
+import { DSpaceObject } from './dspace-object.model';
+import { HALLink } from './HALLink.model';
 import { ResourceType } from './resource-type';
 
 export class Bitstream extends DSpaceObject {
@@ -24,22 +24,24 @@ export class Bitstream extends DSpaceObject {
   bundleName: string;
 
   /**
-   * An array of Bitstream Format of this Bitstream
+   * The Thumbnail for this Bitstream
    */
-  format: Observable<RemoteData<BitstreamFormat>>;
+  thumbnail?: Observable<RemoteData<Bitstream>>;
 
   /**
-   * An array of Items that are direct parents of this Bitstream
+   * The Bitstream Format for this Bitstream
    */
-  parents: Observable<RemoteData<Item[]>>;
-
-  /**
-   * The Bundle that owns this Bitstream
-   */
-  owner: Observable<RemoteData<Item>>;
+  format?: Observable<RemoteData<BitstreamFormat>>;
 
   /**
    * The URL to retrieve this Bitstream's file
    */
   content: string;
+
+  _links: {
+    self: HALLink;
+    bundle: HALLink;
+    content: HALLink;
+    format: HALLink;
+  }
 }
diff --git a/src/app/core/shared/bundle.model.ts b/src/app/core/shared/bundle.model.ts
index dade7d12be..58359e959c 100644
--- a/src/app/core/shared/bundle.model.ts
+++ b/src/app/core/shared/bundle.model.ts
@@ -1,10 +1,6 @@
 import { DSpaceObject } from './dspace-object.model';
-import { Bitstream } from './bitstream.model';
-import { Item } from './item.model';
-import { RemoteData } from '../data/remote-data';
-import { Observable } from 'rxjs';
+import { HALLink } from './HALLink.model';
 import { ResourceType } from './resource-type';
-import { PaginatedList } from '../data/paginated-list';
 
 export class Bundle extends DSpaceObject {
   static type = new ResourceType('bundle');
@@ -14,24 +10,11 @@ export class Bundle extends DSpaceObject {
    */
   name: string;
 
-  /**
-   * The primary bitstream of this Bundle
-   */
-  primaryBitstream: Observable<RemoteData<Bitstream>>;
-
-  /**
-   * An array of Items that are direct parents of this Bundle
-   */
-  parents: Observable<RemoteData<Item[]>>;
-
-  /**
-   * The Item that owns this Bundle
-   */
-  owner: Observable<RemoteData<Item>>;
-
-  /**
-   * List of Bitstreams that are part of this Bundle
-   */
-  bitstreams: Observable<RemoteData<PaginatedList<Bitstream>>>;
-
+  _links: {
+    self: HALLink;
+    primaryBitstream: HALLink;
+    parents: HALLink;
+    owner: HALLink;
+    bitstreams: HALLink;
+  }
 }
diff --git a/src/app/core/shared/collection.model.ts b/src/app/core/shared/collection.model.ts
index 642fe50736..b04ad95bc4 100644
--- a/src/app/core/shared/collection.model.ts
+++ b/src/app/core/shared/collection.model.ts
@@ -1,5 +1,6 @@
 import { DSpaceObject } from './dspace-object.model';
 import { Bitstream } from './bitstream.model';
+import { HALLink } from './HALLink.model';
 import { Item } from './item.model';
 import { RemoteData } from '../data/remote-data';
 import { Observable } from 'rxjs';
@@ -82,4 +83,13 @@ export class Collection extends DSpaceObject {
   owner: Observable<RemoteData<Collection>>;
 
   items: Observable<RemoteData<Item[]>>;
+
+  _links: {
+    license: HALLink;
+    harvester: HALLink;
+    mappedItems: HALLink;
+    defaultAccessConditions: HALLink;
+    logo: HALLink;
+    self: HALLink;
+  }
 }
diff --git a/src/app/core/shared/item.model.ts b/src/app/core/shared/item.model.ts
index bd304274ab..73853dc948 100644
--- a/src/app/core/shared/item.model.ts
+++ b/src/app/core/shared/item.model.ts
@@ -1,19 +1,11 @@
-import { map, startWith, filter, switchMap } from 'rxjs/operators';
-import { Observable } from 'rxjs';
+import { isEmpty } from '../../shared/empty.util';
+import { DEFAULT_ENTITY_TYPE } from '../../shared/metadata-representation/metadata-representation.decorator';
+import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
 
 import { DSpaceObject } from './dspace-object.model';
-import { Collection } from './collection.model';
-import { RemoteData } from '../data/remote-data';
-import { Bitstream } from './bitstream.model';
-import { hasValueOperator, isNotEmpty, isEmpty } from '../../shared/empty.util';
-import { PaginatedList } from '../data/paginated-list';
-import { Relationship } from './item-relationships/relationship.model';
-import { ResourceType } from './resource-type';
-import { getAllSucceededRemoteData, getSucceededRemoteData } from './operators';
-import { Bundle } from './bundle.model';
 import { GenericConstructor } from './generic-constructor';
-import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
-import { DEFAULT_ENTITY_TYPE } from '../../shared/metadata-representation/metadata-representation.decorator';
+import { HALLink } from './HALLink.model';
+import { ResourceType } from './resource-type';
 
 /**
  * Class representing a DSpace Item
@@ -46,77 +38,13 @@ export class Item extends DSpaceObject {
    */
   isWithdrawn: boolean;
 
-  /**
-   * An array of Collections that are direct parents of this Item
-   */
-  parents: Observable<RemoteData<Collection[]>>;
-
-  /**
-   * The Collection that owns this Item
-   */
-  owningCollection: Observable<RemoteData<Collection>>;
-
-  get owner(): Observable<RemoteData<Collection>> {
-    return this.owningCollection;
-  }
-
-  /**
-   * Bitstream bundles within this item
-   */
-  bundles: Observable<RemoteData<PaginatedList<Bundle>>>;
-
-  relationships: Observable<RemoteData<PaginatedList<Relationship>>>;
-
-  /**
-   * Retrieves the thumbnail of this item
-   * @returns {Observable<Bitstream>} the primaryBitstream of the 'THUMBNAIL' bundle
-   */
-  getThumbnail(): Observable<Bitstream> {
-    // TODO: currently this just picks the first thumbnail
-    // should be adjusted when we have a way to determine
-    // the primary thumbnail from rest
-    return this.getBitstreamsByBundleName('THUMBNAIL').pipe(
-      filter((thumbnails) => isNotEmpty(thumbnails)),
-      map((thumbnails) => thumbnails[0]),)
-  }
-
-  /**
-   * Retrieves the thumbnail for the given original of this item
-   * @returns {Observable<Bitstream>} the primaryBitstream of the 'THUMBNAIL' bundle
-   */
-  getThumbnailForOriginal(original: Bitstream): Observable<Bitstream> {
-    return this.getBitstreamsByBundleName('THUMBNAIL').pipe(
-      map((files) => {
-        return files.find((thumbnail) => thumbnail.name.startsWith(original.name))
-      }),startWith(undefined),);
-  }
-
-  /**
-   * Retrieves all files that should be displayed on the item page of this item
-   * @returns {Observable<Array<Observable<Bitstream>>>} an array of all Bitstreams in the 'ORIGINAL' bundle
-   */
-  getFiles(): Observable<Bitstream[]> {
-    return this.getBitstreamsByBundleName('ORIGINAL');
-  }
-
-  /**
-   * Retrieves bitstreams by bundle name
-   * @param bundleName The name of the Bundle that should be returned
-   * @returns {Observable<Bitstream[]>} the bitstreams with the given bundleName
-   * TODO now that bitstreams can be paginated this should move to the server
-   * see https://github.com/DSpace/dspace-angular/issues/332
-   */
-  getBitstreamsByBundleName(bundleName: string): Observable<Bitstream[]> {
-    return this.bundles.pipe(
-      getSucceededRemoteData(),
-      map((rd: RemoteData<PaginatedList<Bundle>>) => rd.payload.page.find((bundle: Bundle) => bundle.name === bundleName)),
-      hasValueOperator(),
-      switchMap((bundle: Bundle) => bundle.bitstreams),
-      getAllSucceededRemoteData(),
-      map((rd: RemoteData<PaginatedList<Bitstream>>) => rd.payload.page),
-      startWith([])
-    );
-  }
+  _links: {
+    self: HALLink;
+    parents: HALLink;
+    owningCollection: HALLink;
+    bundles: HALLink;
+    relationships: HALLink;
+  };
 
   /**
    * Method that returns as which type of object this object should be rendered
diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts
index 308e4f8a2d..57a955b5fc 100644
--- a/src/app/core/shared/operators.ts
+++ b/src/app/core/shared/operators.ts
@@ -59,10 +59,92 @@ export const getRemoteDataPayload = () =>
   <T>(source: Observable<RemoteData<T>>): Observable<T> =>
     source.pipe(map((remoteData: RemoteData<T>) => remoteData.payload));
 
+export const getPaginatedListPayload = () =>
+  <T>(source: Observable<PaginatedList<T>>): Observable<T[]> =>
+    source.pipe(map((list: PaginatedList<T>) => list.page));
+
 export const getSucceededRemoteData = () =>
   <T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
     source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded));
 
+/**
+ * Get the first successful remotely retrieved object
+ *
+ * You usually don't want to use this, it is a code smell.
+ * Work with the RemoteData object instead, that way you can
+ * handle loading and errors correctly.
+ *
+ * These operators were created as a first step in refactoring
+ * out all the instances where this is used incorrectly.
+ */
+export const getFirstSucceededRemoteDataPayload = () =>
+  <T>(source: Observable<RemoteData<T>>): Observable<T> =>
+    source.pipe(
+      getSucceededRemoteData(),
+      getRemoteDataPayload()
+    );
+
+/**
+ * Get the all successful remotely retrieved objects
+ *
+ * You usually don't want to use this, it is a code smell.
+ * Work with the RemoteData object instead, that way you can
+ * handle loading and errors correctly.
+ *
+ * These operators were created as a first step in refactoring
+ * out all the instances where this is used incorrectly.
+ */
+export const getAllSucceededRemoteDataPayload = () =>
+  <T>(source: Observable<RemoteData<T>>): Observable<T> =>
+    source.pipe(
+      getAllSucceededRemoteData(),
+      getRemoteDataPayload()
+    );
+
+/**
+ * Get the first successful remotely retrieved paginated list
+ * as an array
+ *
+ * You usually don't want to use this, it is a code smell.
+ * Work with the RemoteData object instead, that way you can
+ * handle loading and errors correctly.
+ *
+ * You also don't want to ignore pagination and simply use the
+ * page as an array.
+ *
+ * These operators were created as a first step in refactoring
+ * out all the instances where this is used incorrectly.
+ */
+export const getFirstSucceededRemoteListPayload = () =>
+  <T>(source: Observable<RemoteData<PaginatedList<T>>>): Observable<T[]> =>
+    source.pipe(
+      getSucceededRemoteData(),
+      getRemoteDataPayload(),
+      getPaginatedListPayload()
+    );
+
+/**
+ * Get all successful remotely retrieved paginated lists
+ * as arrays
+ *
+ * You usually don't want to use this, it is a code smell.
+ * Work with the RemoteData object instead, that way you can
+ * handle loading and errors correctly.
+ *
+ * You also don't want to ignore pagination and simply use the
+ * page as an array.
+ *
+ * These operators were created as a first step in refactoring
+ * out all the instances where this is used incorrectly.
+ */
+export const getAllSucceededRemoteListPayload = () =>
+  <T>(source: Observable<RemoteData<PaginatedList<T>>>): Observable<T[]> =>
+    source.pipe(
+      getAllSucceededRemoteData(),
+      getRemoteDataPayload(),
+      getPaginatedListPayload()
+    );
+
 /**
  * Operator that checks if a remote data object contains a page not found error
  * When it does contain such an error, it will redirect the user to a page not found, without altering the current URL
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html
index 18ff77bf23..af339109c6 100644
--- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html
@@ -2,13 +2,13 @@
     <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
         <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </a>
         <span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </span>
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html
index 07e50eb6fb..2c7f24662a 100644
--- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html
@@ -2,13 +2,13 @@
         <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
             <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
                 <div>
-                    <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                    <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                     </ds-grid-thumbnail>
                 </div>
             </a>
             <span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
                 <div>
-                    <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                    <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                     </ds-grid-thumbnail>
                 </div>
             </span>
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html
index 394e5241e1..d6b9c4a62e 100644
--- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html
@@ -2,13 +2,13 @@
     <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
         <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </a>
         <span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </span>
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
index 87312f8784..cdfa6293c4 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
+++ b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
@@ -4,7 +4,7 @@
 <div class="row">
   <div class="col-xs-12 col-md-4">
     <ds-metadata-field-wrapper>
-      <ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
+      <ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
     </ds-metadata-field-wrapper>
     <ds-generic-item-page-field [item]="object"
       [fields]="['publicationvolume.volumeNumber']"
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
index e77b24a98b..5df1997f0b 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
+++ b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
@@ -4,7 +4,7 @@
 <div class="row">
   <div class="col-xs-12 col-md-4">
     <ds-metadata-field-wrapper>
-      <ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
+      <ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
     </ds-metadata-field-wrapper>
     <ds-generic-item-page-field [item]="object"
       [fields]="['publicationvolume.volumeNumber']"
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
index e86ab35e0e..d8d32df04a 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
+++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
@@ -4,7 +4,7 @@
 <div class="row">
   <div class="col-xs-12 col-md-4">
     <ds-metadata-field-wrapper>
-      <ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
+      <ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
     </ds-metadata-field-wrapper>
     <ds-generic-item-page-field class="item-page-fields" [item]="object"
       [fields]="['creativeworkseries.issn']"
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html
index 5c42be2b24..0fb1ec02f8 100644
--- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html
@@ -2,13 +2,13 @@
     <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
         <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </a>
         <span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </span>
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html
index b7eed7c8b4..321ecd4a47 100644
--- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html
@@ -3,13 +3,13 @@
         <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
            class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </a>
         <span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </span>
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html
index f3a0dea81f..c39de6bc2a 100644
--- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html
@@ -2,13 +2,13 @@
     <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
         <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </a>
         <span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
       <div>
-        <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+        <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
         </ds-grid-thumbnail>
       </div>
     </span>
diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
index 1b23d567f5..784000b446 100644
--- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
+++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
@@ -4,7 +4,7 @@
 <div class="row">
   <div class="col-xs-12 col-md-4">
     <ds-metadata-field-wrapper>
-      <ds-thumbnail [thumbnail]="object.getThumbnail() | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail>
+      <ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail>
     </ds-metadata-field-wrapper>
     <ds-generic-item-page-field [item]="object"
       [fields]="['organization.foundingDate']"
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html
index 97a3cf416e..af71d5a1c6 100644
--- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html
+++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html
@@ -4,7 +4,7 @@
 <div class="row">
   <div class="col-xs-12 col-md-4">
     <ds-metadata-field-wrapper>
-      <ds-thumbnail [thumbnail]="this.object.getThumbnail() | async" [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>
+      <ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>
     </ds-metadata-field-wrapper>
     <ds-generic-item-page-field [item]="object"
       [fields]="['person.email']"
diff --git a/src/app/entity-groups/research-entities/item-pages/project/project.component.html b/src/app/entity-groups/research-entities/item-pages/project/project.component.html
index bb6ce21cd1..6c4a0bab01 100644
--- a/src/app/entity-groups/research-entities/item-pages/project/project.component.html
+++ b/src/app/entity-groups/research-entities/item-pages/project/project.component.html
@@ -4,7 +4,7 @@
 <div class="row">
   <div class="col-xs-12 col-md-4">
     <ds-metadata-field-wrapper>
-      <ds-thumbnail [thumbnail]="object.getThumbnail() | async" [defaultImage]="'assets/images/project-placeholder.svg'"></ds-thumbnail>
+      <ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/project-placeholder.svg'"></ds-thumbnail>
     </ds-metadata-field-wrapper>
     <!--<ds-generic-item-page-field [item]="object"-->
       <!--[fields]="['project.identifier.status']"-->
diff --git a/src/app/shared/comcol-page-logo/comcol-page-logo.component.html b/src/app/shared/comcol-page-logo/comcol-page-logo.component.html
index 4bd7369f06..057c358223 100644
--- a/src/app/shared/comcol-page-logo/comcol-page-logo.component.html
+++ b/src/app/shared/comcol-page-logo/comcol-page-logo.component.html
@@ -1,3 +1,3 @@
 <div *ngIf="logo" class="dso-logo mb-3">
-    <img [src]="logo.content" class="img-fluid" [attr.alt]="alternateText ? alternateText : null" (error)="errorHandler($event)"/>
+    <img [src]="logo._links.content.href" class="img-fluid" [attr.alt]="alternateText ? alternateText : null" (error)="errorHandler($event)"/>
 </div>
diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts
index 9a8f5ebeec..859a13c0ac 100644
--- a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts
+++ b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts
@@ -1,9 +1,18 @@
 import { Component, Input } from '@angular/core';
 
 import { Observable } from 'rxjs';
-import { first } from 'rxjs/operators';
+import { first, map } from 'rxjs/operators';
+import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
+import { RemoteData } from '../../../../core/data/remote-data';
 
 import { Item } from '../../../../core/shared/item.model';
+import {
+  getAllSucceededRemoteListPayload,
+  getFirstSucceededRemoteDataPayload,
+  getFirstSucceededRemoteListPayload,
+  getRemoteDataPayload,
+  getSucceededRemoteData
+} from '../../../../core/shared/operators';
 import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
 import { fadeInOut } from '../../../animations/fade';
 import { Bitstream } from '../../../../core/shared/bitstream.model';
@@ -64,15 +73,16 @@ export class ItemDetailPreviewComponent {
    * @param {HALEndpointService} halService
    */
   constructor(private fileService: FileService,
-              private halService: HALEndpointService) {
+              private halService: HALEndpointService,
+              private bitstreamDataService: BitstreamDataService) {
   }
 
   /**
    * Initialize all instance variables
    */
   ngOnInit() {
-    this.thumbnail$ = this.item.getThumbnail();
-    this.bitstreams$ = this.item.getFiles();
+    this.thumbnail$ = this.getThumbnail();
+    this.bitstreams$ = this.getFiles();
   }
 
   /**
@@ -86,4 +96,20 @@ export class ItemDetailPreviewComponent {
         this.fileService.downloadFile(fileUrl);
       });
   }
+
+  // TODO refactor this method to return RemoteData, and the template to deal with loading and errors
+  getThumbnail(): Observable<Bitstream> {
+    return this.bitstreamDataService.getThumbnailFor(this.item).pipe(
+      getFirstSucceededRemoteDataPayload()
+    );
+  }
+
+  // TODO refactor this method to return RemoteData, and the template to deal with loading and errors
+  getFiles(): Observable<Bitstream[]> {
+    return this.bitstreamDataService
+      .findAllByItemAndBundleName(this.item, 'ORIGINAL', { elementsPerPage: Number.MAX_SAFE_INTEGER })
+      .pipe(
+        getFirstSucceededRemoteListPayload()
+      );
+  }
 }
diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts
index 6ae0c2d37e..2c8c29ec85 100644
--- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts
+++ b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts
@@ -30,8 +30,8 @@ export class GridThumbnailComponent implements OnInit {
   }
 
   ngOnInit(): void {
-    if (hasValue(this.thumbnail) && this.thumbnail.content) {
-      this.src = this.thumbnail.content;
+    if (hasValue(this.thumbnail) && this.thumbnail._links.content.href) {
+      this.src = this.thumbnail._links.content.href;
     } else {
       this.src = this.defaultImage
     }
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html
index a00e30cbcd..41c16c6eab 100644
--- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html
+++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html
@@ -3,13 +3,13 @@
         <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
            class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="this.dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </a>
         <span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
             <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
+                <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
                 </ds-grid-thumbnail>
             </div>
         </span>
diff --git a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts
index 8587e91302..dc05f78e40 100644
--- a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts
+++ b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts
@@ -1,12 +1,15 @@
-import { Component, Inject, OnInit } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { Observable } from 'rxjs';
 
 import { SearchResult } from '../../search/search-result.model';
+import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
+import { Bitstream } from '../../../core/shared/bitstream.model';
 import { DSpaceObject } from '../../../core/shared/dspace-object.model';
-import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
-import { TruncatableService } from '../../truncatable/truncatable.service';
-import { Observable } from 'rxjs';
 import { Metadata } from '../../../core/shared/metadata.utils';
+import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
 import { hasValue } from '../../empty.util';
+import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { TruncatableService } from '../../truncatable/truncatable.service';
 
 @Component({
   selector: 'ds-search-result-grid-element',
@@ -24,7 +27,10 @@ export class SearchResultGridElementComponent<T extends SearchResult<K>, K exten
    */
   isCollapsed$: Observable<boolean>;
 
-  public constructor(protected truncatableService: TruncatableService) {
+  public constructor(
+    protected truncatableService: TruncatableService,
+    protected bitstreamDataService: BitstreamDataService
+  ) {
     super();
     if (hasValue(this.object)) {
       this.isCollapsed$ = this.isCollapsed();
@@ -63,4 +69,11 @@ export class SearchResultGridElementComponent<T extends SearchResult<K>, K exten
   private isCollapsed(): Observable<boolean> {
     return this.truncatableService.isCollapsed(this.dso.id);
   }
+
+  // TODO refactor to return RemoteData, and thumbnail template to deal with loading
+  getThumbnail(): Observable<Bitstream> {
+    return this.bitstreamDataService.getThumbnailFor(this.dso as any).pipe(
+      getFirstSucceededRemoteDataPayload()
+    );
+  }
 }
diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts
index e31e907b47..73278c7c79 100644
--- a/src/app/thumbnail/thumbnail.component.ts
+++ b/src/app/thumbnail/thumbnail.component.ts
@@ -13,9 +13,22 @@ import { hasValue } from '../shared/empty.util';
   styleUrls: ['./thumbnail.component.scss'],
   templateUrl: './thumbnail.component.html'
 })
-export class ThumbnailComponent implements OnInit {
+export class ThumbnailComponent {
 
-  @Input() thumbnail: Bitstream;
+  private _thumbnail: Bitstream;
+
+  get thumbnail(): Bitstream {
+    return this._thumbnail;
+  };
+
+  @Input() set thumbnail(t: Bitstream) {
+    this._thumbnail = t;
+    if (hasValue(this.thumbnail) && hasValue(this.thumbnail._links.content) && this.thumbnail._links.content.href) {
+      this.src = this.thumbnail._links.content.href;
+    } else {
+      this.src = this.defaultImage
+    }
+  }
 
   /**
    * The default 'holder.js' image
@@ -23,16 +36,8 @@ export class ThumbnailComponent implements OnInit {
   @Input() defaultImage? = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2293%22%20height%3D%22120%22%20viewBox%3D%220%200%2093%20120%22%20preserveAspectRatio%3D%22none%22%3E%3C!--%0ASource%20URL%3A%20holder.js%2F93x120%3Ftext%3DNo%20Thumbnail%0ACreated%20with%20Holder.js%202.8.2.%0ALearn%20more%20at%20http%3A%2F%2Fholderjs.com%0A(c)%202012-2015%20Ivan%20Malopinsky%20-%20http%3A%2F%2Fimsky.co%0A--%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%3C!%5BCDATA%5B%23holder_1543e460b05%20text%20%7B%20fill%3A%23AAAAAA%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A10pt%20%7D%20%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_1543e460b05%22%3E%3Crect%20width%3D%2293%22%20height%3D%22120%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Cg%3E%3Ctext%20x%3D%2235.6171875%22%20y%3D%2257%22%3ENo%3C%2Ftext%3E%3Ctext%20x%3D%2210.8125%22%20y%3D%2272%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E';
 
   src: string;
+
   errorHandler(event) {
     event.currentTarget.src = this.defaultImage;
   }
-
-  ngOnInit(): void {
-      if (hasValue(this.thumbnail) && this.thumbnail.content) {
-        this.src = this.thumbnail.content;
-      } else {
-        this.src = this.defaultImage
-      }
-  }
-
 }
diff --git a/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html b/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html
index 50b5fed9d3..adecd9e1af 100644
--- a/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html
+++ b/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html
@@ -4,7 +4,7 @@ a<div class="top-item-page">
 
             <div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
                 <ds-metadata-field-wrapper>
-                    <ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
+                    <ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
                 </ds-metadata-field-wrapper>
                 <div>
                     <a class="btn btn-secondary"
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
index 9fa80d9c3c..78a0eba13e 100644
--- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
+++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
@@ -4,7 +4,7 @@
 
             <div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
                 <ds-metadata-field-wrapper>
-                    <ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
+                    <ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
                 </ds-metadata-field-wrapper>
                 <div>
                     <a class="btn btn-secondary"
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
index 907f70b941..3dca7dfbba 100644
--- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
+++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
@@ -4,7 +4,7 @@
 
             <div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
                 <ds-metadata-field-wrapper>
-                    <ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
+                    <ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
                 </ds-metadata-field-wrapper>
                 <div>
                     <a class="btn btn-secondary"
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
index 089511804e..ed542c7840 100644
--- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
+++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
@@ -3,7 +3,7 @@
         <div class="row">
             <div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
                 <ds-metadata-field-wrapper>
-                    <ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
+                    <ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
                 </ds-metadata-field-wrapper>
                 <div>
                     <a class="btn btn-secondary"
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
index ee78d9c653..ae003e31a8 100644
--- a/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
@@ -4,7 +4,7 @@
 
             <div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
                 <ds-metadata-field-wrapper>
-                    <ds-thumbnail [thumbnail]="object.getThumbnail() | async"
+                    <ds-thumbnail [thumbnail]="getThumbnail() | async"
                                   [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail>
                 </ds-metadata-field-wrapper>
                 <div>
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
index 1679f9354d..dbcb76a292 100644
--- a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
@@ -4,7 +4,7 @@
 
             <div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
                 <ds-metadata-field-wrapper>
-                    <ds-thumbnail [thumbnail]="object.getThumbnail() | async"
+                    <ds-thumbnail [thumbnail]="getThumbnail() | async"
                                   [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>
                 </ds-metadata-field-wrapper>
                 <div>
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html
index 31ba79a158..b31353ef76 100644
--- a/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html
@@ -4,7 +4,7 @@
 
             <div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
                 <ds-metadata-field-wrapper>
-                    <ds-thumbnail [thumbnail]="object.getThumbnail() | async"
+                    <ds-thumbnail [thumbnail]="getThumbnail() | async"
                                   [defaultImage]="'assets/images/project-placeholder.svg'"></ds-thumbnail>
                 </ds-metadata-field-wrapper>
                 <div>
-- 
GitLab