diff --git a/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..255532c8c4d0ee426aaccda5f79e7a82a53df774
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts
@@ -0,0 +1,120 @@
+import { Inject, Injectable, OnInit } from '@angular/core';
+import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
+import { Observable } from 'rxjs/internal/Observable';
+import { Item } from '../../../core/shared/item.model';
+import { ItemDataService } from '../../../core/data/item-data.service';
+import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
+import { ActivatedRoute, Router } from '@angular/router';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { TranslateService } from '@ngx-translate/core';
+import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
+import { first, map } from 'rxjs/operators';
+import { RemoteData } from '../../../core/data/remote-data';
+import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component';
+
+@Injectable()
+/**
+ * Abstract component for managing object updates of an item
+ */
+export abstract class AbstractItemUpdateComponent extends AbstractTrackableComponent implements OnInit {
+  /**
+   * The item to display the edit page for
+   */
+  item: Item;
+  /**
+   * The current values and updates for all this item's fields
+   * Should be initialized in the initializeUpdates method of the child component
+   */
+  updates$: Observable<FieldUpdates>;
+
+  constructor(
+    public itemService: ItemDataService,
+    public objectUpdatesService: ObjectUpdatesService,
+    public router: Router,
+    public notificationsService: NotificationsService,
+    public translateService: TranslateService,
+    @Inject(GLOBAL_CONFIG) public EnvConfig: GlobalConfig,
+    public route: ActivatedRoute
+  ) {
+    super(objectUpdatesService, notificationsService, translateService)
+  }
+
+  /**
+   * Initialize common properties between item-update components
+   */
+  ngOnInit(): void {
+    this.route.parent.data.pipe(map((data) => data.item))
+      .pipe(
+        first(),
+        map((data: RemoteData<Item>) => data.payload)
+      ).subscribe((item: Item) => {
+      this.item = item;
+    });
+
+    this.discardTimeOut = this.EnvConfig.item.edit.undoTimeout;
+    this.url = this.router.url;
+    if (this.url.indexOf('?') > 0) {
+      this.url = this.url.substr(0, this.url.indexOf('?'));
+    }
+    this.hasChanges().pipe(first()).subscribe((hasChanges) => {
+      if (!hasChanges) {
+        this.initializeOriginalFields();
+      } else {
+        this.checkLastModified();
+      }
+    });
+
+    this.initializeNotificationsPrefix();
+    this.initializeUpdates();
+  }
+
+  /**
+   * Initialize the values and updates of the current item's fields
+   */
+  abstract initializeUpdates(): void;
+
+  /**
+   * Initialize the prefix for notification messages
+   */
+  abstract initializeNotificationsPrefix(): void;
+
+  /**
+   * Sends all initial values of this item to the object updates service
+   */
+  abstract initializeOriginalFields(): void;
+
+  /**
+   * Prevent unnecessary rerendering so fields don't lose focus
+   */
+  trackUpdate(index, update: FieldUpdate) {
+    return update && update.field ? update.field.uuid : undefined;
+  }
+
+  /**
+   * Check if the current page is entirely valid
+   */
+  protected isValid() {
+    return this.objectUpdatesService.isValidPage(this.url);
+  }
+
+  /**
+   * Checks if the current item is still in sync with the version in the store
+   * If it's not, a notification is shown and the changes are removed
+   */
+  private checkLastModified() {
+    const currentVersion = this.item.lastModified;
+    this.objectUpdatesService.getLastModified(this.url).pipe(first()).subscribe(
+      (updateVersion: Date) => {
+        if (updateVersion.getDate() !== currentVersion.getDate()) {
+          this.notificationsService.warning(this.getNotificationTitle('outdated'), this.getNotificationContent('outdated'));
+          this.initializeOriginalFields();
+        }
+      }
+    );
+  }
+
+  /**
+   * Submit the current changes
+   */
+  abstract submit(): void;
+}
diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts
index 6b3e05c818bfa6a795cac2c7543c7d9ca611f3f6..9b1d2c96486855d833c7d8b04bc98c8af850c30f 100644
--- a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts
+++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts
@@ -1,4 +1,4 @@
-import { Component, Inject, Input, OnInit } from '@angular/core';
+import { Component, Inject } from '@angular/core';
 import { Item } from '../../../core/shared/item.model';
 import { ItemDataService } from '../../../core/data/item-data.service';
 import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
@@ -6,8 +6,6 @@ import { ActivatedRoute, Router } from '@angular/router';
 import { cloneDeep } from 'lodash';
 import { Observable } from 'rxjs';
 import {
-  FieldUpdate,
-  FieldUpdates,
   Identifiable
 } from '../../../core/data/object-updates/object-updates.reducer';
 import { first, map, switchMap, take, tap } from 'rxjs/operators';
@@ -20,6 +18,7 @@ import { RegistryService } from '../../../core/registry/registry.service';
 import { MetadataField } from '../../../core/metadata/metadatafield.model';
 import { MetadatumViewModel } from '../../../core/shared/metadata.models';
 import { Metadata } from '../../../core/shared/metadata.utils';
+import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
 
 @Component({
   selector: 'ds-item-metadata',
@@ -29,28 +28,7 @@ import { Metadata } from '../../../core/shared/metadata.utils';
 /**
  * Component for displaying an item's metadata edit page
  */
-export class ItemMetadataComponent implements OnInit {
-
-  /**
-   * The item to display the edit page for
-   */
-  item: Item;
-  /**
-   * The current values and updates for all this item's metadata fields
-   */
-  updates$: Observable<FieldUpdates>;
-  /**
-   * The current url of this page
-   */
-  url: string;
-  /**
-   * The time span for being able to undo discarding changes
-   */
-  private discardTimeOut: number;
-  /**
-   * Prefix for this component's notification translate keys
-   */
-  private notificationsPrefix = 'item.edit.metadata.notifications.';
+export class ItemMetadataComponent extends AbstractItemUpdateComponent {
 
   /**
    * Observable with a list of strings with all existing metadata field keys
@@ -58,90 +36,60 @@ export class ItemMetadataComponent implements OnInit {
   metadataFields$: Observable<string[]>;
 
   constructor(
-    private itemService: ItemDataService,
-    private objectUpdatesService: ObjectUpdatesService,
-    private router: Router,
-    private notificationsService: NotificationsService,
-    private translateService: TranslateService,
-    @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
-    private route: ActivatedRoute,
-    private metadataFieldService: RegistryService,
+    public itemService: ItemDataService,
+    public objectUpdatesService: ObjectUpdatesService,
+    public router: Router,
+    public notificationsService: NotificationsService,
+    public translateService: TranslateService,
+    @Inject(GLOBAL_CONFIG) public EnvConfig: GlobalConfig,
+    public route: ActivatedRoute,
+    public metadataFieldService: RegistryService,
   ) {
-
+    super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route);
   }
 
   /**
    * Set up and initialize all fields
    */
   ngOnInit(): void {
+    super.ngOnInit();
     this.metadataFields$ = this.findMetadataFields();
-    this.route.parent.data.pipe(map((data) => data.item))
-      .pipe(
-        first(),
-        map((data: RemoteData<Item>) => data.payload)
-      ).subscribe((item: Item) => {
-      this.item = item;
-    });
-
-    this.discardTimeOut = this.EnvConfig.item.edit.undoTimeout;
-    this.url = this.router.url;
-    if (this.url.indexOf('?') > 0) {
-      this.url = this.url.substr(0, this.url.indexOf('?'));
-    }
-    this.hasChanges().pipe(first()).subscribe((hasChanges) => {
-      if (!hasChanges) {
-        this.initializeOriginalFields();
-      } else {
-        this.checkLastModified();
-      }
-    });
-    this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList);
   }
 
   /**
-   * Sends a new add update for a field to the object updates service
-   * @param metadata The metadata to add, if no parameter is supplied, create a new Metadatum
+   * Initialize the values and updates of the current item's metadata fields
    */
-  add(metadata: MetadatumViewModel = new MetadatumViewModel()) {
-    this.objectUpdatesService.saveAddFieldUpdate(this.url, metadata);
-
-  }
+  public initializeUpdates(): void {
+    this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList);
+    }
 
   /**
-   * Request the object updates service to discard all current changes to this item
-   * Shows a notification to remind the user that they can undo this
+   * Initialize the prefix for notification messages
    */
-  discard() {
-    const undoNotification = this.notificationsService.info(this.getNotificationTitle('discarded'), this.getNotificationContent('discarded'), { timeOut: this.discardTimeOut });
-    this.objectUpdatesService.discardFieldUpdates(this.url, undoNotification);
+  public initializeNotificationsPrefix(): void {
+    this.notificationsPrefix = 'item.edit.metadata.notifications.';
   }
 
   /**
-   * Request the object updates service to undo discarding all changes to this item
+   * Sends a new add update for a field to the object updates service
+   * @param metadata The metadata to add, if no parameter is supplied, create a new Metadatum
    */
-  reinstate() {
-    this.objectUpdatesService.reinstateFieldUpdates(this.url);
+  add(metadata: MetadatumViewModel = new MetadatumViewModel()) {
+    this.objectUpdatesService.saveAddFieldUpdate(this.url, metadata);
   }
 
   /**
    * Sends all initial values of this item to the object updates service
    */
-  private initializeOriginalFields() {
+  public initializeOriginalFields() {
     this.objectUpdatesService.initialize(this.url, this.item.metadataAsList, this.item.lastModified);
   }
 
-  /**
-   * Prevent unnecessary rerendering so fields don't lose focus
-   */
-  trackUpdate(index, update: FieldUpdate) {
-    return update && update.field ? update.field.uuid : undefined;
-  }
-
   /**
    * Requests all current metadata for this item and requests the item service to update the item
    * Makes sure the new version of the item is rendered on the page
    */
-  submit() {
+  public submit() {
     this.isValid().pipe(first()).subscribe((isValid) => {
       if (isValid) {
         const metadata$: Observable<Identifiable[]> = this.objectUpdatesService.getUpdatedFields(this.url, this.item.metadataAsList) as Observable<MetadatumViewModel[]>;
@@ -167,60 +115,6 @@ export class ItemMetadataComponent implements OnInit {
     });
   }
 
-  /**
-   * Checks whether or not there are currently updates for this item
-   */
-  hasChanges(): Observable<boolean> {
-    return this.objectUpdatesService.hasUpdates(this.url);
-  }
-
-  /**
-   * Checks whether or not the item is currently reinstatable
-   */
-  isReinstatable(): Observable<boolean> {
-    return this.objectUpdatesService.isReinstatable(this.url);
-  }
-
-  /**
-   * Checks if the current item is still in sync with the version in the store
-   * If it's not, a notification is shown and the changes are removed
-   */
-  private checkLastModified() {
-    const currentVersion = this.item.lastModified;
-    this.objectUpdatesService.getLastModified(this.url).pipe(first()).subscribe(
-      (updateVersion: Date) => {
-        if (updateVersion.getDate() !== currentVersion.getDate()) {
-          this.notificationsService.warning(this.getNotificationTitle('outdated'), this.getNotificationContent('outdated'));
-          this.initializeOriginalFields();
-        }
-      }
-    );
-  }
-
-  /**
-   * Check if the current page is entirely valid
-   */
-  private isValid() {
-    return this.objectUpdatesService.isValidPage(this.url);
-  }
-
-  /**
-   * Get translated notification title
-   * @param key
-   */
-  private getNotificationTitle(key: string) {
-    return this.translateService.instant(this.notificationsPrefix + key + '.title');
-  }
-
-  /**
-   * Get translated notification content
-   * @param key
-   */
-  private getNotificationContent(key: string) {
-    return this.translateService.instant(this.notificationsPrefix + key + '.content');
-
-  }
-
   /**
    * Method to request all metadata fields and convert them to a list of strings
    */
diff --git a/src/app/shared/trackable/abstract-trackable.component.ts b/src/app/shared/trackable/abstract-trackable.component.ts
index cd1b425f1058387864523fd35747aac4aa257340..e1a99d90b9422b5229aaaade17557d57fa7acccf 100644
--- a/src/app/shared/trackable/abstract-trackable.component.ts
+++ b/src/app/shared/trackable/abstract-trackable.component.ts
@@ -63,7 +63,7 @@ export class AbstractTrackableComponent {
    * Get translated notification title
    * @param key
    */
-  private getNotificationTitle(key: string) {
+  getNotificationTitle(key: string) {
     return this.translateService.instant(this.notificationsPrefix + key + '.title');
   }
 
@@ -71,7 +71,7 @@ export class AbstractTrackableComponent {
    * Get translated notification content
    * @param key
    */
-  private getNotificationContent(key: string) {
+  getNotificationContent(key: string) {
     return this.translateService.instant(this.notificationsPrefix + key + '.content');
 
   }