From 4e679ec6db48e391f90a2424f4b8cbabb6aa2d86 Mon Sep 17 00:00:00 2001
From: Samuel <samuel@atmire.com>
Date: Thu, 9 Jan 2020 13:42:50 +0100
Subject: [PATCH] PR-530 Keep virtual metadata on relationship delete -
 community feedback

---
 .../edit-relationship-list.component.html     |  27 +-
 .../edit-relationship-list.component.spec.ts  | 123 +++++---
 .../edit-relationship-list.component.ts       | 105 ++++---
 .../edit-relationship.component.html          |   4 +-
 .../edit-relationship.component.ts            |   2 +-
 .../item-relationships.component.html         |   9 +-
 .../item-relationships.component.ts           |  59 ++--
 .../virtual-metadata.component.html           |  48 ++-
 .../virtual-metadata.component.spec.ts        | 274 +++++++-----------
 .../virtual-metadata.component.ts             |  96 ++++--
 src/app/core/core.module.ts                   |   2 +
 src/app/core/data/entity-type.service.ts      | 103 +++++++
 .../object-updates/object-updates.actions.ts  |  13 +-
 .../object-updates/object-updates.service.ts  |  43 +--
 src/app/core/data/relationship.service.ts     | 108 +++----
 src/app/core/shared/hal-endpoint.service.ts   |   4 +-
 16 files changed, 587 insertions(+), 433 deletions(-)
 create mode 100644 src/app/core/data/entity-type.service.ts

diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html
index 8edcb6808e..1a7cc2e2df 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html
+++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html
@@ -1,16 +1,15 @@
-<ng-container *ngVar="(updates$ | async) as updates">
-  <div *ngIf="updates">
-    <h5>{{getRelationshipMessageKey(relationshipLabel) | translate}}</h5>
-    <ng-container *ngVar="(updates | dsObjectValues) as updateValues">
-      <div *ngFor="let updateValue of updateValues; trackBy: trackUpdate"
-           ds-edit-relationship
-           class="relationship-row d-block"
-           [fieldUpdate]="updateValue || {}"
-           [url]="url"
-           [editItem]="item"
-           [ngClass]="{'alert alert-danger': updateValue.changeType === 2}">
-      </div>
-      <ds-loading *ngIf="updateValues.length == 0" message="{{'loading.items' | translate}}"></ds-loading>
+<h5>{{getRelationshipMessageKey() | async | translate}}</h5>
+<ng-container *ngVar="updates$ | async as updates">
+    <ng-container *ngIf="updates">
+        <ng-container *ngVar="updates | dsObjectValues as updateValues">
+            <ds-edit-relationship *ngFor="let updateValue of updateValues; trackBy: trackUpdate"
+                                  class="relationship-row d-block"
+                                  [fieldUpdate]="updateValue"
+                                  [url]="url"
+                                  [editItem]="item"
+                                  [ngClass]="{'alert alert-danger': updateValue?.changeType === 2}">
+            </ds-edit-relationship>
+        </ng-container>
     </ng-container>
-  </div>
+    <div *ngIf="!updates">no relationships</div>
 </ng-container>
diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts
index bc2fcda3a9..9b2bb3c992 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts
+++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts
@@ -1,27 +1,26 @@
-import { EditRelationshipListComponent } from './edit-relationship-list.component';
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
-import { ResourceType } from '../../../../core/shared/resource-type';
-import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
-import { of as observableOf } from 'rxjs/internal/observable/of';
-import { RemoteData } from '../../../../core/data/remote-data';
-import { Item } from '../../../../core/shared/item.model';
-import { PaginatedList } from '../../../../core/data/paginated-list';
-import { PageInfo } from '../../../../core/shared/page-info.model';
-import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
-import { SharedModule } from '../../../../shared/shared.module';
-import { TranslateModule } from '@ngx-translate/core';
-import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
-import { RelationshipService } from '../../../../core/data/relationship.service';
-import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
-import { By } from '@angular/platform-browser';
+import {EditRelationshipListComponent} from './edit-relationship-list.component';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {RelationshipType} from '../../../../core/shared/item-relationships/relationship-type.model';
+import {Relationship} from '../../../../core/shared/item-relationships/relationship.model';
+import {of as observableOf} from 'rxjs/internal/observable/of';
+import {RemoteData} from '../../../../core/data/remote-data';
+import {Item} from '../../../../core/shared/item.model';
+import {PaginatedList} from '../../../../core/data/paginated-list';
+import {PageInfo} from '../../../../core/shared/page-info.model';
+import {FieldChangeType} from '../../../../core/data/object-updates/object-updates.actions';
+import {SharedModule} from '../../../../shared/shared.module';
+import {TranslateModule} from '@ngx-translate/core';
+import {ObjectUpdatesService} from '../../../../core/data/object-updates/object-updates.service';
+import {DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';
+import {By} from '@angular/platform-browser';
+import {ItemType} from '../../../../core/shared/item-relationships/item-type.model';
 
 let comp: EditRelationshipListComponent;
 let fixture: ComponentFixture<EditRelationshipListComponent>;
 let de: DebugElement;
 
 let objectUpdatesService;
-let relationshipService;
+let entityTypeService;
 
 const url = 'http://test-url.com/test-url';
 
@@ -30,42 +29,65 @@ let author1;
 let author2;
 let fieldUpdate1;
 let fieldUpdate2;
-let relationships;
+let relationship1;
+let relationship2;
 let relationshipType;
+let entityType;
+let relatedEntityType;
 
 describe('EditRelationshipListComponent', () => {
   beforeEach(async(() => {
+
     relationshipType = Object.assign(new RelationshipType(), {
       id: '1',
       uuid: '1',
       leftwardType: 'isAuthorOfPublication',
-      rightwardType: 'isPublicationOfAuthor'
+      rightwardType: 'isPublicationOfAuthor',
+      leftType: observableOf(new RemoteData(false, false, true, undefined, entityType)),
+      rightType: observableOf(new RemoteData(false, false, true, undefined, relatedEntityType)),
+    });
+
+    relationship1 = Object.assign(new Relationship(), {
+      self: url + '/2',
+      id: '2',
+      uuid: '2',
+      leftId: 'author1',
+      rightId: 'publication',
+      leftItem: observableOf(new RemoteData(false, false, true, undefined, item)),
+      rightItem: observableOf(new RemoteData(false, false, true, undefined, author1)),
+      relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType))
     });
 
-    relationships = [
-      Object.assign(new Relationship(), {
-        self: url + '/2',
-        id: '2',
-        uuid: '2',
-        leftId: 'author1',
-        rightId: 'publication',
-        relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType))
-      }),
-      Object.assign(new Relationship(), {
-        self: url + '/3',
-        id: '3',
-        uuid: '3',
-        leftId: 'author2',
-        rightId: 'publication',
-        relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType))
-      })
-    ];
+    relationship2 = Object.assign(new Relationship(), {
+      self: url + '/3',
+      id: '3',
+      uuid: '3',
+      leftId: 'author2',
+      rightId: 'publication',
+      leftItem: observableOf(new RemoteData(false, false, true, undefined, item)),
+      rightItem: observableOf(new RemoteData(false, false, true, undefined, author2)),
+      relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType))
+    });
 
     item = Object.assign(new Item(), {
       self: 'fake-item-url/publication',
       id: 'publication',
       uuid: 'publication',
-      relationships: observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(new PageInfo(), relationships)))
+      relationships: observableOf(new RemoteData(
+        false,
+        false,
+        true,
+        undefined,
+        new PaginatedList(new PageInfo(), [relationship1, relationship2])
+      ))
+    });
+
+    entityType = Object.assign(new ItemType(), {
+      id: 'entityType',
+    });
+
+    relatedEntityType = Object.assign(new ItemType(), {
+      id: 'relatedEntityType',
     });
 
     author1 = Object.assign(new Item(), {
@@ -88,17 +110,29 @@ describe('EditRelationshipListComponent', () => {
 
     objectUpdatesService = jasmine.createSpyObj('objectUpdatesService',
       {
-        getFieldUpdatesExclusive: observableOf({
+        getFieldUpdates: observableOf({
           [author1.uuid]: fieldUpdate1,
           [author2.uuid]: fieldUpdate2
         })
       }
     );
 
-    relationshipService = jasmine.createSpyObj('relationshipService',
+    entityTypeService = jasmine.createSpyObj('entityTypeService',
       {
-        getRelatedItemsByLabel: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [author1, author2]))),
-        getItemRelationshipsByLabel: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), relationships))),
+        getEntityTypeByLabel: observableOf(new RemoteData(
+          false,
+          false,
+          true,
+          null,
+          entityType,
+        )),
+        getEntityTypeRelationships: observableOf(new RemoteData(
+          false,
+          false,
+          true,
+          null,
+          new PaginatedList(new PageInfo(), [relationshipType]),
+        )),
       }
     );
 
@@ -107,7 +141,6 @@ describe('EditRelationshipListComponent', () => {
       declarations: [EditRelationshipListComponent],
       providers: [
         { provide: ObjectUpdatesService, useValue: objectUpdatesService },
-        { provide: RelationshipService, useValue: relationshipService }
       ], schemas: [
         NO_ERRORS_SCHEMA
       ]
@@ -120,7 +153,7 @@ describe('EditRelationshipListComponent', () => {
     de = fixture.debugElement;
     comp.item = item;
     comp.url = url;
-    comp.relationshipLabel = relationshipType.leftwardType;
+    comp.relationshipType = relationshipType;
     fixture.detectChanges();
   });
 
diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts
index 7645b2b4b7..13214e27cf 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts
+++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts
@@ -1,12 +1,15 @@
-import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
-import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
-import { Observable } from 'rxjs/internal/Observable';
-import { FieldUpdate, FieldUpdates } from '../../../../core/data/object-updates/object-updates.reducer';
-import { RelationshipService } from '../../../../core/data/relationship.service';
-import { Item } from '../../../../core/shared/item.model';
-import { map, switchMap} from 'rxjs/operators';
-import { hasValue } from '../../../../shared/empty.util';
+import {Component, Input, OnInit} from '@angular/core';
+import {ObjectUpdatesService} from '../../../../core/data/object-updates/object-updates.service';
+import {Observable} from 'rxjs/internal/Observable';
+import {FieldUpdate, FieldUpdates} from '../../../../core/data/object-updates/object-updates.reducer';
+import {Item} from '../../../../core/shared/item.model';
+import {map, switchMap} from 'rxjs/operators';
+import {hasValue} from '../../../../shared/empty.util';
 import {Relationship} from '../../../../core/shared/item-relationships/relationship.model';
+import {RelationshipType} from '../../../../core/shared/item-relationships/relationship-type.model';
+import {getRemoteDataPayload, getSucceededRemoteData} from '../../../../core/shared/operators';
+import {combineLatest as observableCombineLatest, combineLatest} from 'rxjs';
+import {ItemType} from '../../../../core/shared/item-relationships/item-type.model';
 
 @Component({
   selector: 'ds-edit-relationship-list',
@@ -17,12 +20,15 @@ import {Relationship} from '../../../../core/shared/item-relationships/relations
  * A component creating a list of editable relationships of a certain type
  * The relationships are rendered as a list of related items
  */
-export class EditRelationshipListComponent implements OnInit, OnChanges {
+export class EditRelationshipListComponent implements OnInit {
+
   /**
    * The item to display related items for
    */
   @Input() item: Item;
 
+  @Input() itemType: ItemType;
+
   /**
    * The URL to the current page
    * Used to fetch updates for the current item from the store
@@ -32,7 +38,7 @@ export class EditRelationshipListComponent implements OnInit, OnChanges {
   /**
    * The label of the relationship-type we're rendering a list for
    */
-  @Input() relationshipLabel: string;
+  @Input() relationshipType: RelationshipType;
 
   /**
    * The FieldUpdates for the relationships in question
@@ -41,48 +47,42 @@ export class EditRelationshipListComponent implements OnInit, OnChanges {
 
   constructor(
     protected objectUpdatesService: ObjectUpdatesService,
-    protected relationshipService: RelationshipService
   ) {
   }
 
-  ngOnInit(): void {
-    this.initUpdates();
-  }
-
-  ngOnChanges(changes: SimpleChanges): void {
-    this.initUpdates();
-  }
-
   /**
-   * Initialize the FieldUpdates using the related items
+   * Get the i18n message key for this relationship type
    */
-  initUpdates() {
-    this.updates$ = this.getUpdatesByLabel(this.relationshipLabel);
-  }
+  public getRelationshipMessageKey(): Observable<string> {
 
-  /**
-   * Get FieldUpdates for the relationships of a specific type
-   * @param label   The relationship type's label
-   */
-  public getUpdatesByLabel(label: string): Observable<FieldUpdates> {
-    return this.relationshipService.getItemRelationshipsByLabel(this.item, label).pipe(
-      map((relationsRD) => relationsRD.payload.page.map((relationship) =>
-        Object.assign(new Relationship(), relationship, {uuid: relationship.id})
-      )),
-      switchMap((initialFields) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, initialFields)),
+    return this.getLabel().pipe(
+      map((label) => {
+        if (hasValue(label) && label.indexOf('Of') > -1) {
+          return `relationships.${label.substring(0, label.indexOf('Of') + 2)}`
+        } else {
+          return label;
+        }
+      }),
     );
   }
 
   /**
-   * Get the i18n message key for a relationship
-   * @param label   The relationship type's label
+   * Get the relevant label for this relationship type
    */
-  public getRelationshipMessageKey(label: string): string {
-    if (hasValue(label) && label.indexOf('Of') > -1) {
-      return `relationships.${label.substring(0, label.indexOf('Of') + 2)}`
-    } else {
-      return label;
-    }
+  private getLabel(): Observable<string> {
+
+    return combineLatest([
+      this.relationshipType.leftType,
+      this.relationshipType.rightType,
+    ].map((itemTypeRD) => itemTypeRD.pipe(
+      getSucceededRemoteData(),
+      getRemoteDataPayload(),
+    ))).pipe(
+      map((itemTypes) => [
+        this.relationshipType.leftwardType,
+        this.relationshipType.rightwardType,
+      ][itemTypes.findIndex((itemType) => itemType.id === this.itemType.id)]),
+    );
   }
 
   /**
@@ -91,4 +91,27 @@ export class EditRelationshipListComponent implements OnInit, OnChanges {
   trackUpdate(index, update: FieldUpdate) {
     return update && update.field ? update.field.uuid : undefined;
   }
+
+  ngOnInit(): void {
+    this.updates$ = this.item.relationships.pipe(
+      map((relationships) => relationships.payload.page.filter((relationship) => relationship)),
+      switchMap((itemRelationships) =>
+        observableCombineLatest(
+          itemRelationships
+            .map((relationship) => relationship.relationshipType.pipe(
+              getSucceededRemoteData(),
+              getRemoteDataPayload(),
+            ))
+        ).pipe(
+          map((relationshipTypes) => itemRelationships.filter(
+            (relationship, index) => relationshipTypes[index].id === this.relationshipType.id)
+          ),
+          map((relationships) => relationships.map((relationship) =>
+            Object.assign(new Relationship(), relationship, {uuid: relationship.id})
+          )),
+        )
+      ),
+      switchMap((initialFields) => this.objectUpdatesService.getFieldUpdates(this.url, initialFields)),
+    );
+  }
 }
diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html
index 245bf04051..7e61e8958f 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html
+++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html
@@ -19,7 +19,9 @@
 </div>
 <ng-template #virtualMetadataModal>
   <ds-virtual-metadata
-          [relationship]="relationship"
+          [relationshipId]="relationship.id"
+          [leftItem]="leftItem$ | async"
+          [rightItem]="rightItem$ | async"
           [url]="url"
           (close)="closeVirtualMetadataModal()"
           (save)="remove()"
diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts
index 3e24801992..e8ab8b38db 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts
+++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts
@@ -13,7 +13,7 @@ import {NgbModal, NgbModalRef} from '@ng-bootstrap/ng-bootstrap';
 
 @Component({
   // tslint:disable-next-line:component-selector
-  selector: '[ds-edit-relationship]',
+  selector: 'ds-edit-relationship',
   styleUrls: ['./edit-relationship.component.scss'],
   templateUrl: './edit-relationship.component.html',
 })
diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html
index 4bd0b3df2c..384a469f24 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html
+++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html
@@ -17,8 +17,13 @@
       <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
     </button>
   </div>
-  <div *ngFor="let label of relationLabels$ | async" class="mb-4">
-    <ds-edit-relationship-list [item]="item" [url]="url" [relationshipLabel]="label" ></ds-edit-relationship-list>
+  <div *ngFor="let relationshipType of relationshipTypes$ | async" class="mb-4">
+    <ds-edit-relationship-list
+            [url]="url"
+            [item]="item"
+            [itemType]="entityType$ | async"
+            [relationshipType]="relationshipType"
+    ></ds-edit-relationship-list>
   </div>
   <div class="button-row bottom">
     <div class="float-right">
diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts
index 1e678884b1..2030b07f50 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts
+++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts
@@ -22,10 +22,12 @@ import { ErrorResponse, RestResponse } from '../../../core/cache/response.models
 import { isNotEmptyOperator } from '../../../shared/empty.util';
 import { RemoteData } from '../../../core/data/remote-data';
 import { ObjectCacheService } from '../../../core/cache/object-cache.service';
-import { getSucceededRemoteData} from '../../../core/shared/operators';
+import {getRemoteDataPayload, getSucceededRemoteData} from '../../../core/shared/operators';
 import { RequestService } from '../../../core/data/request.service';
 import { Subscription } from 'rxjs/internal/Subscription';
-import { getRelationsByRelatedItemIds } from '../../simple/item-types/shared/item-relationships-utils';
+import {RelationshipType} from '../../../core/shared/item-relationships/relationship-type.model';
+import {ItemType} from '../../../core/shared/item-relationships/item-type.model';
+import {EntityTypeService} from '../../../core/data/entity-type.service';
 
 @Component({
   selector: 'ds-item-relationships',
@@ -40,13 +42,14 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
   /**
    * The labels of all different relations within this item
    */
-  relationLabels$: Observable<string[]>;
+  relationshipTypes$: Observable<RelationshipType[]>;
 
   /**
    * A subscription that checks when the item is deleted in cache and reloads the item by sending a new request
    * This is used to update the item in cache after relationships are deleted
    */
   itemUpdateSubscription: Subscription;
+  entityType$: Observable<ItemType>;
 
   constructor(
     protected itemService: ItemDataService,
@@ -59,7 +62,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
     protected relationshipService: RelationshipService,
     protected objectCache: ObjectCacheService,
     protected requestService: RequestService,
-    protected cdRef: ChangeDetectorRef
+    protected entityTypeService: EntityTypeService,
   ) {
     super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route);
   }
@@ -69,21 +72,13 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
    */
   ngOnInit(): void {
     super.ngOnInit();
-    this.relationLabels$ = this.relationshipService.getItemRelationshipLabels(this.item);
-    this.initializeItemUpdate();
-  }
-
-  /**
-   * Update the item (and view) when it's removed in the request cache
-   */
-  public initializeItemUpdate(): void {
     this.itemUpdateSubscription = this.requestService.hasByHrefObservable(this.item.self).pipe(
       filter((exists: boolean) => !exists),
       switchMap(() => this.itemService.findById(this.item.uuid)),
       getSucceededRemoteData(),
     ).subscribe((itemRD: RemoteData<Item>) => {
       this.item = itemRD.payload;
-      this.cdRef.detectChanges();
+      this.initializeUpdates();
     });
   }
 
@@ -91,8 +86,22 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
    * Initialize the values and updates of the current item's relationship fields
    */
   public initializeUpdates(): void {
-    this.updates$ = this.relationshipService.getRelatedItems(this.item).pipe(
-      switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdates(this.url, items))
+
+    this.entityType$ = this.entityTypeService.getEntityTypeByLabel(
+      this.item.firstMetadataValue('relationship.type')
+    ).pipe(
+      getSucceededRemoteData(),
+      getRemoteDataPayload(),
+    );
+
+    this.relationshipTypes$ = this.entityType$.pipe(
+      switchMap((entityType) =>
+        this.entityTypeService.getEntityTypeRelationships(entityType.id).pipe(
+          getSucceededRemoteData(),
+          getRemoteDataPayload(),
+          map((relationshipTypes) => relationshipTypes.page),
+        )
+      ),
     );
   }
 
@@ -142,8 +151,9 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
         ))
       ),
     ).subscribe((responses: RestResponse[]) => {
-      this.displayNotifications(responses);
-      this.reset();
+      this.itemUpdateSubscription.add(() => {
+        this.displayNotifications(responses);
+      });
     });
   }
 
@@ -165,22 +175,12 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
     }
   }
 
-  /**
-   * Re-initialize fields and subscriptions
-   */
-  reset() {
-    this.initializeOriginalFields();
-    this.initializeUpdates();
-    this.initializeItemUpdate();
-  }
-
   /**
    * Sends all initial values of this item to the object updates service
    */
   public initializeOriginalFields() {
-    this.relationshipService.getRelatedItems(this.item).pipe(take(1)).subscribe((items: Item[]) => {
-      this.objectUpdatesService.initialize(this.url, items, this.item.lastModified);
-    });
+    const initialFields = [];
+    this.objectUpdatesService.initialize(this.url, initialFields, this.item.lastModified);
   }
 
   /**
@@ -189,5 +189,4 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
   ngOnDestroy(): void {
     this.itemUpdateSubscription.unsubscribe();
   }
-
 }
diff --git a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html
index 8a0269d6b7..f5dab71884 100644
--- a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html
+++ b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html
@@ -5,37 +5,33 @@
         </button>
     </div>
     <div class="modal-body">
-        <div *ngFor="let item$ of [leftItem$, rightItem$]">
-            <div *ngVar="item$ | async as item">
-                <div *ngVar="(isSelectedVirtualMetadataItem(item) | async) as selected"
-                     (click)="setSelectedVirtualMetadataItem(item, !selected)"
-                     class="d-flex flex-row">
-                    <div class="m-2">
-                        <label>
-                            <input type="checkbox" [checked]="selected">
-                        </label>
-                    </div>
-                    <div class="flex-column">
-                        <ds-listable-object-component-loader
-                                [object]="item$ | async"></ds-listable-object-component-loader>
-                        <div *ngFor="let metadata of getVirtualMetadata(relationship, item$ | async)">
-                            <div>
-                                <div class="font-weight-bold">
-                                    {{metadata.metadataField}}
-                                </div>
-                                <div>
-                                    {{metadata.metadataValue.value}}
-                                </div>
-                            </div>
+        <ng-container *ngFor="let item of items; trackBy: trackItem">
+            <div *ngVar="(isSelectedVirtualMetadataItem(item) | async) as selected"
+                 (click)="setSelectedVirtualMetadataItem(item, !selected)"
+                 class="d-flex flex-row">
+                <div class="m-2">
+                    <label>
+                        <input class="select" type="checkbox">
+                    </label>
+                </div>
+                <div class="flex-column">
+                    <ds-listable-object-component-loader [object]="item">
+                    </ds-listable-object-component-loader>
+                    <div *ngFor="let metadata of virtualMetadata.get(item.uuid)">
+                        <div class="font-weight-bold">
+                            {{metadata.metadataField}}
+                        </div>
+                        <div>
+                            {{metadata.metadataValue.value}}
                         </div>
                     </div>
                 </div>
             </div>
-        </div>
+        </ng-container>
         <div class="d-flex flex-row-reverse m-2">
-            <button class="btn btn-primary"
-                    (click)="save.emit()"><i
-                    class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}}
+            <button class="btn btn-primary save"
+                    (click)="save.emit()">
+                <i class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}}
             </button>
         </div>
     </div>
diff --git a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.spec.ts b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.spec.ts
index 7b37b238e8..5b72bec46d 100644
--- a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.spec.ts
+++ b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.spec.ts
@@ -1,172 +1,102 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { Item } from '../../../core/shared/item.model';
-import { RouterStub } from '../../../shared/testing/router-stub';
-import { CommonModule } from '@angular/common';
-import { RouterTestingModule } from '@angular/router/testing';
-import { TranslateModule } from '@ngx-translate/core';
-import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
-import { ActivatedRoute, Router } from '@angular/router';
-import { VirtualMetadataComponent } from './virtual-metadata.component';
-import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
-import { NotificationsService } from '../../../shared/notifications/notifications.service';
-import { SearchService } from '../../../+search-page/search-service/search.service';
-import { of as observableOf } from 'rxjs';
-import { FormsModule } from '@angular/forms';
-import { ItemDataService } from '../../../core/data/item-data.service';
-import { RemoteData } from '../../../core/data/remote-data';
-import { PaginatedList } from '../../../core/data/paginated-list';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { RestResponse } from '../../../core/cache/response.models';
-import { Collection } from '../../../core/shared/collection.model';
-
-// describe('ItemMoveComponent', () => {
-//   let comp: VirtualMetadataComponent;
-//   let fixture: ComponentFixture<VirtualMetadataComponent>;
-//
-//   const mockItem = Object.assign(new Item(), {
-//     id: 'fake-id',
-//     handle: 'fake/handle',
-//     lastModified: '2018'
-//   });
-//
-//   const itemPageUrl = `fake-url/${mockItem.id}`;
-//   const routerStub = Object.assign(new RouterStub(), {
-//     url: `${itemPageUrl}/edit`
-//   });
-//
-//   const mockItemDataService = jasmine.createSpyObj({
-//     moveToCollection: observableOf(new RestResponse(true, 200, 'Success'))
-//   });
-//
-//   const mockItemDataServiceFail = jasmine.createSpyObj({
-//     moveToCollection: observableOf(new RestResponse(false, 500, 'Internal server error'))
-//   });
-//
-//   const routeStub = {
-//     data: observableOf({
-//       item: new RemoteData(false, false, true, null, {
-//         id: 'item1'
-//       })
-//     })
-//   };
-//
-//   const collection1 = Object.assign(new Collection(),{
-//     uuid: 'collection-uuid-1',
-//     name: 'Test collection 1',
-//     self: 'self-link-1',
-//   });
-//
-//   const collection2 = Object.assign(new Collection(),{
-//     uuid: 'collection-uuid-2',
-//     name: 'Test collection 2',
-//     self: 'self-link-2',
-//   });
-//
-//   const mockSearchService = {
-//     search: () => {
-//       return observableOf(new RemoteData(false, false, true, null,
-//         new PaginatedList(null, [
-//           {
-//             indexableObject: collection1,
-//             hitHighlights: {}
-//           }, {
-//             indexableObject: collection2,
-//             hitHighlights: {}
-//           }
-//         ])));
-//     }
-//   };
-//
-//   const notificationsServiceStub = new NotificationsServiceStub();
-//
-//   describe('ItemMoveComponent success', () => {
-//     beforeEach(async(() => {
-//       TestBed.configureTestingModule({
-//         imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
-//         declarations: [VirtualMetadataComponent],
-//         providers: [
-//           {provide: ActivatedRoute, useValue: routeStub},
-//           {provide: Router, useValue: routerStub},
-//           {provide: ItemDataService, useValue: mockItemDataService},
-//           {provide: NotificationsService, useValue: notificationsServiceStub},
-//           {provide: SearchService, useValue: mockSearchService},
-//         ], schemas: [
-//           CUSTOM_ELEMENTS_SCHEMA
-//         ]
-//       }).compileComponents();
-//     }));
-//
-//     beforeEach(() => {
-//       fixture = TestBed.createComponent(VirtualMetadataComponent);
-//       comp = fixture.componentInstance;
-//       fixture.detectChanges();
-//     });
-//     it('should load suggestions', () => {
-//       const expected = [
-//         collection1,
-//         collection2
-//       ];
-//
-//       comp.collectionSearchResults.subscribe((value) => {
-//           expect(value).toEqual(expected);
-//         }
-//       );
-//     });
-//     it('should get current url ', () => {
-//       expect(comp.getCurrentUrl()).toEqual('fake-url/fake-id/edit');
-//     });
-//     it('should on click select the correct collection name and id', () => {
-//       const data = collection1;
-//
-//       comp.onClick(data);
-//
-//       expect(comp.selectedCollectionName).toEqual('Test collection 1');
-//       expect(comp.selectedCollection).toEqual(collection1);
-//     });
-//     describe('moveCollection', () => {
-//       it('should call itemDataService.moveToCollection', () => {
-//         comp.itemId = 'item-id';
-//         comp.selectedCollectionName = 'selected-collection-id';
-//         comp.selectedCollection = collection1;
-//         comp.moveCollection();
-//
-//         expect(mockItemDataService.moveToCollection).toHaveBeenCalledWith('item-id', collection1);
-//       });
-//       it('should call notificationsService success message on success', () => {
-//         comp.moveCollection();
-//
-//         expect(notificationsServiceStub.success).toHaveBeenCalled();
-//       });
-//     });
-//   });
-//
-//   describe('ItemMoveComponent fail', () => {
-//     beforeEach(async(() => {
-//       TestBed.configureTestingModule({
-//         imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
-//         declarations: [VirtualMetadataComponent],
-//         providers: [
-//           {provide: ActivatedRoute, useValue: routeStub},
-//           {provide: Router, useValue: routerStub},
-//           {provide: ItemDataService, useValue: mockItemDataServiceFail},
-//           {provide: NotificationsService, useValue: notificationsServiceStub},
-//           {provide: SearchService, useValue: mockSearchService},
-//         ], schemas: [
-//           CUSTOM_ELEMENTS_SCHEMA
-//         ]
-//       }).compileComponents();
-//     }));
-//
-//     beforeEach(() => {
-//       fixture = TestBed.createComponent(VirtualMetadataComponent);
-//       comp = fixture.componentInstance;
-//       fixture.detectChanges();
-//     });
-//
-//     it('should call notificationsService error message on fail', () => {
-//       comp.moveCollection();
-//
-//       expect(notificationsServiceStub.error).toHaveBeenCalled();
-//     });
-//   });
-// });
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {of as observableOf} from 'rxjs/internal/observable/of';
+import {TranslateModule} from '@ngx-translate/core';
+import {DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';
+import {By} from '@angular/platform-browser';
+import {VirtualMetadataComponent} from './virtual-metadata.component';
+import {Item} from '../../../core/shared/item.model';
+import {ObjectUpdatesService} from '../../../core/data/object-updates/object-updates.service';
+import {VarDirective} from '../../../shared/utils/var.directive';
+
+fdescribe('VirtualMetadataComponent', () => {
+
+  let comp: VirtualMetadataComponent;
+  let fixture: ComponentFixture<VirtualMetadataComponent>;
+  let de: DebugElement;
+
+  let objectUpdatesService;
+
+  const url = 'http://test-url.com/test-url';
+
+  let item;
+  let relatedItem;
+  let relationshipId;
+
+  beforeEach(() => {
+
+    relationshipId = 'relationship id';
+
+    item = Object.assign(new Item(), {
+      uuid: 'publication',
+      metadata: [],
+    });
+
+    relatedItem = Object.assign(new Item(), {
+      uuid: 'relatedItem',
+      metadata: [],
+    });
+
+    objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', {
+      isSelectedVirtualMetadata: observableOf(false),
+      setSelectedVirtualMetadata: null,
+    });
+
+    TestBed.configureTestingModule({
+      imports: [TranslateModule.forRoot()],
+      declarations: [VirtualMetadataComponent, VarDirective],
+      providers: [
+        {provide: ObjectUpdatesService, useValue: objectUpdatesService},
+      ], schemas: [
+        NO_ERRORS_SCHEMA
+      ]
+    }).compileComponents();
+    fixture = TestBed.createComponent(VirtualMetadataComponent);
+    comp = fixture.componentInstance;
+    de = fixture.debugElement;
+
+    comp.url = url;
+    comp.leftItem = item;
+    comp.rightItem = relatedItem;
+    comp.relationshipId = relationshipId;
+
+    fixture.detectChanges();
+  });
+
+  describe('when clicking the save button', () => {
+    it('should emit a save event', () => {
+
+      spyOn(comp.save, 'emit');
+      fixture.debugElement
+        .query(By.css('button.save'))
+        .triggerEventHandler('click', null);
+      expect(comp.save.emit).toHaveBeenCalled();
+    });
+  });
+
+  describe('when clicking the close button', () => {
+    it('should emit a close event', () => {
+
+      spyOn(comp.close, 'emit');
+      fixture.debugElement
+        .query(By.css('button.close'))
+        .triggerEventHandler('click', null);
+      expect(comp.close.emit).toHaveBeenCalled();
+    });
+  });
+
+  describe('when selecting an item', () => {
+    it('should call the updates service setSelectedVirtualMetadata method', () => {
+      fixture.debugElement
+        .query(By.css('input.select'))
+        .triggerEventHandler('click', null);
+      fixture.whenStable().then(() =>
+        expect(objectUpdatesService.setSelectedVirtualMetadata).toHaveBeenCalledWith(
+          url,
+          relationshipId,
+          item.uuid,
+          true
+        )
+      );
+    });
+  })
+});
diff --git a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.ts b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.ts
index 6a99080c1f..cac46724f0 100644
--- a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.ts
+++ b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.ts
@@ -1,10 +1,7 @@
-import {Component, EventEmitter, Input, OnChanges, Output} from '@angular/core';
-import {ActivatedRoute} from '@angular/router';
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
 import {Observable} from 'rxjs';
 import {Item} from '../../../core/shared/item.model';
-import {Relationship} from '../../../core/shared/item-relationships/relationship.model';
 import {MetadataValue} from '../../../core/shared/metadata.models';
-import {getRemoteDataPayload, getSucceededRemoteData} from '../../../core/shared/operators';
 import {ObjectUpdatesService} from '../../../core/data/object-updates/object-updates.service';
 
 @Component({
@@ -12,47 +9,67 @@ import {ObjectUpdatesService} from '../../../core/data/object-updates/object-upd
   templateUrl: './virtual-metadata.component.html'
 })
 /**
- * Component that handles the moving of an item to a different collection
+ * Component that lists both items of a relationship, along with their virtual metadata of the relationship.
+ * The component is shown when a relationship is marked to be deleted.
+ * Each item has a checkbox to indicate whether its virtual metadata should be saved as real metadata.
  */
-export class VirtualMetadataComponent implements OnChanges {
+export class VirtualMetadataComponent implements OnInit {
 
   /**
    * The current url of this page
    */
   @Input() url: string;
 
-  @Input() relationship: Relationship;
+  /**
+   * The id of the relationship to be deleted.
+   */
+  @Input() relationshipId: string;
+
+  /**
+   * The left item of the relationship to be deleted.
+   */
+  @Input() leftItem: Item;
 
+  /**
+   * The right item of the relationship to be deleted.
+   */
+  @Input() rightItem: Item;
+
+  /**
+   * Emits when the close button is pressed.
+   */
   @Output() close = new EventEmitter();
+
+  /**
+   * Emits when the save button is pressed.
+   */
   @Output() save = new EventEmitter();
 
-  leftItem$: Observable<Item>;
-  rightItem$: Observable<Item>;
+  /**
+   * Get an array of the left and the right item of the relationship to be deleted.
+   */
+  get items() {
+    return [this.leftItem, this.rightItem];
+  }
+
+  private virtualMetadata: Map<string, VirtualMetadata[]> = new Map<string, VirtualMetadata[]>();
 
   constructor(
-    protected route: ActivatedRoute,
     protected objectUpdatesService: ObjectUpdatesService,
   ) {
   }
 
-  ngOnChanges(): void {
-    this.leftItem$ = this.relationship.leftItem.pipe(
-      getSucceededRemoteData(),
-      getRemoteDataPayload(),
-    );
-    this.rightItem$ = this.relationship.rightItem.pipe(
-      getSucceededRemoteData(),
-      getRemoteDataPayload(),
-    );
-  }
-
-  getVirtualMetadata(relationship: Relationship, relatedItem: Item): VirtualMetadata[] {
+  /**
+   * Get the virtual metadata of a given item corresponding to this relationship.
+   * @param item  the item to get the virtual metadata for
+   */
+  getVirtualMetadata(item: Item): VirtualMetadata[] {
 
-    return Object.entries(relatedItem.metadata)
+    return Object.entries(item.metadata)
       .map(([key, value]) =>
         value
           .filter((metadata: MetadataValue) =>
-            metadata.authority && metadata.authority.endsWith(relationship.id))
+            !key.startsWith('relation') && metadata.authority && metadata.authority.endsWith(this.relationshipId))
           .map((metadata: MetadataValue) => {
             return {
               metadataField: key,
@@ -60,18 +77,43 @@ export class VirtualMetadataComponent implements OnChanges {
             }
           })
       )
-      .reduce((previous, current) => previous.concat(current));
+      .reduce((previous, current) => previous.concat(current), []);
   }
 
+  /**
+   * Select/deselect the virtual metadata of an item to be saved as real metadata.
+   * @param item      the item for which (not) to save the virtual metadata as real metadata
+   * @param selected  whether or not to save the virtual metadata as real metadata
+   */
   setSelectedVirtualMetadataItem(item: Item, selected: boolean) {
-    this.objectUpdatesService.setSelectedVirtualMetadata(this.url, this.relationship.id, item.uuid, selected);
+    this.objectUpdatesService.setSelectedVirtualMetadata(this.url, this.relationshipId, item.uuid, selected);
   }
 
+  /**
+   * Check whether the virtual metadata of a given item is selected to be saved as real metadata
+   * @param item  the item for which to check whether the virtual metadata is selected to be saved as real metadata
+   */
   isSelectedVirtualMetadataItem(item: Item): Observable<boolean> {
-    return this.objectUpdatesService.isSelectedVirtualMetadata(this.url, this.relationship.id, item.uuid);
+    return this.objectUpdatesService.isSelectedVirtualMetadata(this.url, this.relationshipId, item.uuid);
+  }
+
+  /**
+   * Prevent unnecessary rerendering so fields don't lose focus
+   */
+  trackItem(index, item: Item) {
+    return item && item.uuid;
+  }
+
+  ngOnInit(): void {
+    this.items.forEach((item) => {
+      this.virtualMetadata.set(item.uuid, this.getVirtualMetadata(item));
+    });
   }
 }
 
+/**
+ * Represents a virtual metadata entry.
+ */
 export interface VirtualMetadata {
   metadataField: string,
   metadataValue: MetadataValue,
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index a03cc6a9cb..8b623e38e4 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -121,6 +121,7 @@ import { NormalizedBrowseEntry } from './shared/normalized-browse-entry.model';
 import { BrowseDefinition } from './shared/browse-definition.model';
 import { MappedCollectionsReponseParsingService } from './data/mapped-collections-reponse-parsing.service';
 import { ObjectSelectService } from '../shared/object-select/object-select.service';
+import {EntityTypeService} from './data/entity-type.service';
 
 const IMPORTS = [
   CommonModule,
@@ -211,6 +212,7 @@ const PROVIDERS = [
   TaskResponseParsingService,
   ClaimedTaskDataService,
   PoolTaskDataService,
+  EntityTypeService,
   // register AuthInterceptor as HttpInterceptor
   {
     provide: HTTP_INTERCEPTORS,
diff --git a/src/app/core/data/entity-type.service.ts b/src/app/core/data/entity-type.service.ts
new file mode 100644
index 0000000000..583601d898
--- /dev/null
+++ b/src/app/core/data/entity-type.service.ts
@@ -0,0 +1,103 @@
+import { DataService } from './data.service';
+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 { HALEndpointService } from '../shared/hal-endpoint.service';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { HttpClient } from '@angular/common/http';
+import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
+import { Injectable } from '@angular/core';
+import { GetRequest } from './request.models';
+import { Observable } from 'rxjs/internal/Observable';
+import {switchMap, take, tap} from 'rxjs/operators';
+import { RemoteData } from './remote-data';
+import {RelationshipType} from '../shared/item-relationships/relationship-type.model';
+import {PaginatedList} from './paginated-list';
+import {ItemType} from '../shared/item-relationships/item-type.model';
+
+/**
+ * Service handling all ItemType requests
+ */
+@Injectable()
+export class EntityTypeService extends DataService<ItemType> {
+
+  protected linkPath = 'entitytypes';
+  protected forceBypassCache = false;
+
+  constructor(protected requestService: RequestService,
+              protected rdbService: RemoteDataBuildService,
+              protected dataBuildService: NormalizedObjectBuildService,
+              protected store: Store<CoreState>,
+              protected halService: HALEndpointService,
+              protected objectCache: ObjectCacheService,
+              protected notificationsService: NotificationsService,
+              protected http: HttpClient,
+              protected comparator: DefaultChangeAnalyzer<ItemType>) {
+    super();
+  }
+
+  getBrowseEndpoint(options, linkPath?: string): Observable<string> {
+    return this.halService.getEndpoint(this.linkPath);
+  }
+
+  /**
+   * Get the endpoint for the item type's allowed relationship types
+   * @param entityTypeId
+   */
+  getRelationshipTypesEndpoint(entityTypeId: string): Observable<string> {
+    return this.halService.getEndpoint(this.linkPath).pipe(
+      switchMap((href) => this.halService.getEndpoint('relationshiptypes', `${href}/${entityTypeId}`))
+    );
+  }
+
+  /**
+   * Get the allowed relationship types for an entity type
+   * @param entityTypeId
+   */
+  getEntityTypeRelationships(entityTypeId: string): Observable<RemoteData<PaginatedList<RelationshipType>>> {
+
+    const href$ = this.getRelationshipTypesEndpoint(entityTypeId);
+
+    href$.pipe(take(1)).subscribe((href) => {
+      const request = new GetRequest(this.requestService.generateRequestId(), href);
+      this.requestService.configure(request);
+    });
+
+    return this.rdbService.buildList(href$);
+  }
+
+  /**
+   * Get an entity type by their label
+   * @param label
+   */
+  getEntityTypeByLabel(label: string): Observable<RemoteData<ItemType>> {
+
+    // TODO: Remove mock data once REST API supports this
+    /*
+    href$.pipe(take(1)).subscribe((href) => {
+      const request = new GetRequest(this.requestService.generateRequestId(), href);
+      this.requestService.configure(request);
+    });
+
+    return this.rdbService.buildSingle<EntityType>(href$);
+    */
+
+    // Mock:
+    const index = [
+      'Publication',
+      'Person',
+      'Project',
+      'OrgUnit',
+      'Journal',
+      'JournalVolume',
+      'JournalIssue',
+      'DataPackage',
+      'DataFile',
+    ].indexOf(label);
+
+    return this.findById((index + 1) + '');
+  }
+}
diff --git a/src/app/core/data/object-updates/object-updates.actions.ts b/src/app/core/data/object-updates/object-updates.actions.ts
index 17ad145eb6..a3a95369fd 100644
--- a/src/app/core/data/object-updates/object-updates.actions.ts
+++ b/src/app/core/data/object-updates/object-updates.actions.ts
@@ -84,6 +84,9 @@ export class AddFieldUpdateAction implements Action {
   }
 }
 
+/**
+ * An ngrx action to select/deselect virtual metadata in the ObjectUpdates state for a certain page url
+ */
 export class SelectVirtualMetadataAction implements Action {
 
   type = ObjectUpdatesActionTypes.SELECT_VIRTUAL_METADATA;
@@ -95,12 +98,16 @@ export class SelectVirtualMetadataAction implements Action {
   };
 
   /**
-   * Create a new AddFieldUpdateAction
+   * Create a new SelectVirtualMetadataAction
    *
    * @param url
    *    the unique url of the page for which a field update is added
-   * @param field The identifiable field of which a new update is added
-   * @param changeType The update's change type
+   * @param source
+   *    the id of the relationship which adds the virtual metadata
+   * @param uuid
+   *    the id of the item which has the virtual metadata
+   * @param select
+   *    whether to select or deselect the virtual metadata to be saved as real metadata
    */
   constructor(
     url: string,
diff --git a/src/app/core/data/object-updates/object-updates.service.ts b/src/app/core/data/object-updates/object-updates.service.ts
index 448f7c9f38..5872a500f7 100644
--- a/src/app/core/data/object-updates/object-updates.service.ts
+++ b/src/app/core/data/object-updates/object-updates.service.ts
@@ -23,13 +23,9 @@ import {
   SetEditableFieldUpdateAction,
   SetValidFieldUpdateAction
 } from './object-updates.actions';
-import {distinctUntilChanged, filter, map} from 'rxjs/operators';
+import {distinctUntilChanged, filter, map, switchMap} from 'rxjs/operators';
 import {hasNoValue, hasValue, isEmpty, isNotEmpty} from '../../../shared/empty.util';
 import {INotification} from '../../../shared/notifications/models/notification.model';
-import {Item} from '../../shared/item.model';
-import {Relationship} from '../../shared/item-relationships/relationship.model';
-import {MetadataValue} from '../../shared/metadata.models';
-import {VirtualMetadata} from '../../../+item-page/edit-item-page/virtual-metadata/virtual-metadata.component';
 
 function objectUpdatesStateSelector(): MemoizedSelector<CoreState, ObjectUpdatesState> {
   return createSelector(coreSelector, (state: CoreState) => state['cache/object-updates']);
@@ -101,18 +97,22 @@ export class ObjectUpdatesService {
    */
   getFieldUpdates(url: string, initialFields: Identifiable[]): Observable<FieldUpdates> {
     const objectUpdates = this.getObjectEntry(url);
-    return objectUpdates.pipe(map((objectEntry) => {
-      const fieldUpdates: FieldUpdates = {};
-      Object.keys(objectEntry.fieldStates).forEach((uuid) => {
-        let fieldUpdate = objectEntry.fieldUpdates[uuid];
-        if (isEmpty(fieldUpdate)) {
-          const identifiable = initialFields.find((object: Identifiable) => object.uuid === uuid);
-          fieldUpdate = { field: identifiable, changeType: undefined };
-        }
-        fieldUpdates[uuid] = fieldUpdate;
-      });
-      return fieldUpdates;
-    }))
+    return objectUpdates.pipe(
+      switchMap((objectEntry) => {
+        const fieldUpdates: FieldUpdates = {};
+        Object.keys(objectEntry.fieldStates).forEach((uuid) => {
+          fieldUpdates[uuid] = objectEntry.fieldUpdates[uuid];
+        });
+        return this.getFieldUpdatesExclusive(url, initialFields).pipe(
+          map((fieldUpdatesExclusive) => {
+            Object.keys(fieldUpdatesExclusive).forEach((uuid) => {
+              fieldUpdates[uuid] = fieldUpdatesExclusive[uuid];
+            });
+            return fieldUpdates;
+          })
+        );
+      }),
+    );
   }
 
   /**
@@ -204,6 +204,15 @@ export class ObjectUpdatesService {
   saveChangeFieldUpdate(url: string, field: Identifiable) {
     this.saveFieldUpdate(url, field, FieldChangeType.UPDATE);
   }
+
+  /**
+   * Check whether the virtual metadata of a given item is selected to be saved as real metadata
+   * @param url           The URL of the page on which the field resides
+   * @param relationship  The id of the relationship for which to check whether the virtual metadata is selected to be
+   *                      saved as real metadata
+   * @param item          The id of the item for which to check whether the virtual metadata is selected to be
+   *                      saved as real metadata
+   */
   isSelectedVirtualMetadata(url: string, relationship: string, item: string): Observable<boolean> {
 
     return this.store
diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts
index 895cb46ed1..377703f335 100644
--- a/src/app/core/data/relationship.service.ts
+++ b/src/app/core/data/relationship.service.ts
@@ -1,39 +1,40 @@
-import { Injectable } from '@angular/core';
-import { RequestService } from './request.service';
-import { HALEndpointService } from '../shared/hal-endpoint.service';
-import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
-import { hasValue, hasValueOperator, isNotEmptyOperator } from '../../shared/empty.util';
-import { distinctUntilChanged, filter, flatMap, map, switchMap, take, tap } from 'rxjs/operators';
+import {Injectable} from '@angular/core';
+import {RequestService} from './request.service';
+import {HALEndpointService} from '../shared/hal-endpoint.service';
+import {RemoteDataBuildService} from '../cache/builders/remote-data-build.service';
+import {hasValue, hasValueOperator, isNotEmptyOperator} from '../../shared/empty.util';
+import {distinctUntilChanged, filter, flatMap, map, switchMap, take, tap} from 'rxjs/operators';
 import {
   configureRequest,
-  filterSuccessfulResponses,
-  getRemoteDataPayload, getResponseFromEntry,
+  getRemoteDataPayload,
+  getResponseFromEntry,
   getSucceededRemoteData
 } from '../shared/operators';
-import { DeleteRequest, FindAllOptions, RestRequest } from './request.models';
-import { Observable } from 'rxjs/internal/Observable';
-import { RestResponse } from '../cache/response.models';
-import { Item } from '../shared/item.model';
-import { Relationship } from '../shared/item-relationships/relationship.model';
-import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
-import { RemoteData } from './remote-data';
-import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
-import { zip as observableZip } from 'rxjs';
-import { PaginatedList } from './paginated-list';
-import { ItemDataService } from './item-data.service';
+import {DeleteRequest, FindAllOptions, RestRequest} from './request.models';
+import {Observable} from 'rxjs/internal/Observable';
+import {RestResponse} from '../cache/response.models';
+import {Item} from '../shared/item.model';
+import {Relationship} from '../shared/item-relationships/relationship.model';
+import {RelationshipType} from '../shared/item-relationships/relationship-type.model';
+import {RemoteData} from './remote-data';
+import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest';
+import {zip as observableZip} from 'rxjs';
+import {PaginatedList} from './paginated-list';
+import {ItemDataService} from './item-data.service';
 import {
-  compareArraysUsingIds, filterRelationsByTypeLabel, paginatedRelationsToItems,
+  compareArraysUsingIds,
+  paginatedRelationsToItems,
   relationsToItems
 } from '../../+item-page/simple/item-types/shared/item-relationships-utils';
-import { ObjectCacheService } from '../cache/object-cache.service';
-import { DataService } from './data.service';
-import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
-import { Store } from '@ngrx/store';
-import { CoreState } from '../core.reducers';
-import { NotificationsService } from '../../shared/notifications/notifications.service';
+import {ObjectCacheService} from '../cache/object-cache.service';
+import {DataService} from './data.service';
+import {NormalizedObjectBuildService} from '../cache/builders/normalized-object-build.service';
+import {Store} from '@ngrx/store';
+import {CoreState} from '../core.reducers';
+import {NotificationsService} from '../../shared/notifications/notifications.service';
 import {HttpClient} from '@angular/common/http';
-import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
-import { SearchParam } from '../cache/models/search-param.model';
+import {DefaultChangeAnalyzer} from './default-change-analyzer.service';
+import {SearchParam} from '../cache/models/search-param.model';
 
 /**
  * The service handling all relationship requests
@@ -93,19 +94,12 @@ export class RelationshipService extends DataService<Relationship> {
       configureRequest(this.requestService),
       switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)),
       getResponseFromEntry(),
-      tap(() => this.clearRelatedCache(uuid))
-    );
-  }
-
-  /**
-   * Get a combined observable containing an array of all relationships in an item, as well as an array of the relationships their types
-   * This is used for easier access of a relationship's type because they exist as observables
-   * @param item
-   */
-  getItemResolvedRelsAndTypes(item: Item): Observable<[Relationship[], RelationshipType[]]> {
-    return observableCombineLatest(
-      this.getItemRelationshipsArray(item),
-      this.getItemRelationshipTypesArray(item)
+      take(1),
+      switchMap((response) =>
+        this.clearRelatedCache(uuid).pipe(
+          map(() => response),
+        )
+      ),
     );
   }
 
@@ -252,23 +246,33 @@ export class RelationshipService extends DataService<Relationship> {
     } else {
       findAllOptions.searchParams = searchParams;
     }
-    return this.searchBy('byLabel', findAllOptions);
+    return this.searchBy('byLabel', findAllOptions).pipe(
+      tap((relationshipsRD) => relationshipsRD.payload.page.forEach(
+        (relationship) => console.log('relationship: ' + relationship.id))
+      ),
+    );
   }
 
   /**
    * Clear object and request caches of the items related to a relationship (left and right items)
    * @param uuid
    */
-  clearRelatedCache(uuid: string) {
-    this.findById(uuid).pipe(
+  clearRelatedCache(uuid: string): Observable<void> {
+    return this.findById(uuid).pipe(
       getSucceededRemoteData(),
-      flatMap((rd: RemoteData<Relationship>) => observableCombineLatest(rd.payload.leftItem.pipe(getSucceededRemoteData()), rd.payload.rightItem.pipe(getSucceededRemoteData()))),
-      take(1)
-    ).subscribe(([leftItem, rightItem]) => {
-      this.objectCache.remove(leftItem.payload.self);
-      this.objectCache.remove(rightItem.payload.self);
-      this.requestService.removeByHrefSubstring(leftItem.payload.self);
-      this.requestService.removeByHrefSubstring(rightItem.payload.self);
-    });
+      switchMap((rd: RemoteData<Relationship>) =>
+        observableCombineLatest(
+          rd.payload.leftItem.pipe(getSucceededRemoteData()),
+          rd.payload.rightItem.pipe(getSucceededRemoteData())
+        )
+      ),
+      take(1),
+      map(([leftItem, rightItem]) => {
+        this.objectCache.remove(leftItem.payload.self);
+        this.objectCache.remove(rightItem.payload.self);
+        this.requestService.removeByHrefSubstring(leftItem.payload.self);
+        this.requestService.removeByHrefSubstring(rightItem.payload.self);
+      }),
+    );
   }
 }
diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts
index a93d54db64..117cc074ca 100644
--- a/src/app/core/shared/hal-endpoint.service.ts
+++ b/src/app/core/shared/hal-endpoint.service.ts
@@ -43,8 +43,8 @@ export class HALEndpointService {
       );
   }
 
-  public getEndpoint(linkPath: string): Observable<string> {
-    return this.getEndpointAt(this.getRootHref(), ...linkPath.split('/'));
+  public getEndpoint(linkPath: string, startHref?: string): Observable<string> {
+    return this.getEndpointAt(startHref || this.getRootHref(), ...linkPath.split('/'));
   }
 
   /**
-- 
GitLab