diff --git a/resources/i18n/en.json b/resources/i18n/en.json
index 63bc5983042ef89081adf3b9703df4b276f63ff4..234967231e32e38e7786b2090c9288d3ebfbaf7b 100644
--- a/resources/i18n/en.json
+++ b/resources/i18n/en.json
@@ -2,16 +2,47 @@
   "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ",
   "404.link.home-page": "Take me to the home page",
   "404.page-not-found": "page not found",
+  "admin.registries.bitstream-formats.create.failure.content": "An error occurred while creating the new bitstream format.",
+  "admin.registries.bitstream-formats.create.failure.head": "Failure",
+  "admin.registries.bitstream-formats.create.head": "Create Bitstream format",
+  "admin.registries.bitstream-formats.create.new": "Add a new bitstream format",
+  "admin.registries.bitstream-formats.create.success.content": "The new bitstream format was successfully created.",
+  "admin.registries.bitstream-formats.create.success.head": "Success",
+  "admin.registries.bitstream-formats.delete.failure.amount": "Failed to remove {{ amount }} format(s)",
+  "admin.registries.bitstream-formats.delete.failure.head": "Failure",
+  "admin.registries.bitstream-formats.delete.success.amount": "Successfully removed {{ amount }} format(s)",
+  "admin.registries.bitstream-formats.delete.success.head": "Success",
   "admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.",
-  "admin.registries.bitstream-formats.formats.no-items": "No bitstream formats to show.",
-  "admin.registries.bitstream-formats.formats.table.internal": "internal",
-  "admin.registries.bitstream-formats.formats.table.mimetype": "MIME Type",
-  "admin.registries.bitstream-formats.formats.table.name": "Name",
-  "admin.registries.bitstream-formats.formats.table.supportLevel.0": "Unknown",
-  "admin.registries.bitstream-formats.formats.table.supportLevel.1": "Known",
-  "admin.registries.bitstream-formats.formats.table.supportLevel.2": "Support",
-  "admin.registries.bitstream-formats.formats.table.supportLevel.head": "Support Level",
+  "admin.registries.bitstream-formats.edit.description.hint": "",
+  "admin.registries.bitstream-formats.edit.description.label": "Description",
+  "admin.registries.bitstream-formats.edit.extensions.hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format.",
+  "admin.registries.bitstream-formats.edit.extensions.label": "File extensions",
+  "admin.registries.bitstream-formats.edit.extensions.placeholder": "Enter a file extenstion without the dot",
+  "admin.registries.bitstream-formats.edit.failure.content": "An error occurred while editing the bitstream format.",
+  "admin.registries.bitstream-formats.edit.failure.head": "Failure",
+  "admin.registries.bitstream-formats.edit.head": "Bitstream format: {{ format }}",
+  "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are are hidden from the user, and used for administrative purposes.",
+  "admin.registries.bitstream-formats.edit.internal.label": "Internal",
+  "admin.registries.bitstream-formats.edit.mimetype.hint": "The MIME type associated with this format, does not have to be unique.",
+  "admin.registries.bitstream-formats.edit.mimetype.label": "MIME Type",
+  "admin.registries.bitstream-formats.edit.shortDescription.hint": "A unique name for this format, (e.g. Microsoft Word XP or Microsoft Word 2000)",
+  "admin.registries.bitstream-formats.edit.shortDescription.label": "Name",
+  "admin.registries.bitstream-formats.edit.success.content": "The bitstream format was successfully edited.",
+  "admin.registries.bitstream-formats.edit.success.head": "Success",
+  "admin.registries.bitstream-formats.edit.supportLevel.hint": "The level of support your institution pledges for this format.",
+  "admin.registries.bitstream-formats.edit.supportLevel.label": "Support level",
   "admin.registries.bitstream-formats.head": "Bitstream Format Registry",
+  "admin.registries.bitstream-formats.no-items": "No bitstream formats to show.",
+  "admin.registries.bitstream-formats.table.delete": "Delete selected",
+  "admin.registries.bitstream-formats.table.deselect-all": "Deselect all",
+  "admin.registries.bitstream-formats.table.internal": "internal",
+  "admin.registries.bitstream-formats.table.mimetype": "MIME Type",
+  "admin.registries.bitstream-formats.table.name": "Name",
+  "admin.registries.bitstream-formats.table.return": "Return",
+  "admin.registries.bitstream-formats.table.supportLevel.KNOWN": "Known",
+  "admin.registries.bitstream-formats.table.supportLevel.SUPPORTED": "Supported",
+  "admin.registries.bitstream-formats.table.supportLevel.UNKNOWN": "Unknown",
+  "admin.registries.bitstream-formats.table.supportLevel.head": "Support Level",
   "admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry",
   "admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.",
   "admin.registries.metadata.form.create": "Create metadata schema",
@@ -101,6 +132,7 @@
   "collection.form.tableofcontents": "News (HTML)",
   "collection.form.title": "Name",
   "collection.page.browse.recent.head": "Recent Submissions",
+  "collection.page.browse.recent.empty": "No items to show",
   "collection.page.license": "License",
   "collection.page.news": "News",
   "community.create.head": "Create a Community",
@@ -659,4 +691,4 @@
   "uploader.or": ", or",
   "uploader.processing": "Processing",
   "uploader.queue-lenght": "Queue length"
-}
+}
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/admin-registries-routing.module.ts b/src/app/+admin/admin-registries/admin-registries-routing.module.ts
index 8e3c322bc827a9616de2bc4ddd5da2a9dc2386f4..afdc46bf172a41b27130edcc379bfb92eb02861a 100644
--- a/src/app/+admin/admin-registries/admin-registries-routing.module.ts
+++ b/src/app/+admin/admin-registries/admin-registries-routing.module.ts
@@ -2,14 +2,29 @@ import { MetadataRegistryComponent } from './metadata-registry/metadata-registry
 import { RouterModule } from '@angular/router';
 import { NgModule } from '@angular/core';
 import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
-import { BitstreamFormatsComponent } from './bitstream-formats/bitstream-formats.component';
+import { URLCombiner } from '../../core/url-combiner/url-combiner';
+import { getRegistriesModulePath } from '../admin-routing.module';
+
+const BITSTREAMFORMATS_MODULE_PATH = 'bitstream-formats';
+
+export function getBitstreamFormatsModulePath() {
+  return new URLCombiner(getRegistriesModulePath(), BITSTREAMFORMATS_MODULE_PATH).toString();
+}
 
 @NgModule({
   imports: [
     RouterModule.forChild([
-      { path: 'metadata', component: MetadataRegistryComponent, data: { title: 'admin.registries.metadata.title' } },
-      { path: 'metadata/:schemaName', component: MetadataSchemaComponent, data: { title: 'admin.registries.schema.title' } },
-      { path: 'bitstream-formats', component: BitstreamFormatsComponent, data: { title: 'admin.registries.bitstream-formats.title' } },
+      {path: 'metadata', component: MetadataRegistryComponent, data: {title: 'admin.registries.metadata.title'}},
+      {
+        path: 'metadata/:schemaName',
+        component: MetadataSchemaComponent,
+        data: {title: 'admin.registries.schema.title'}
+      },
+      {
+        path: BITSTREAMFORMATS_MODULE_PATH,
+        loadChildren: './bitstream-formats/bitstream-formats.module#BitstreamFormatsModule',
+        data: {title: 'admin.registries.bitstream-formats.title'}
+      },
     ])
   ]
 })
diff --git a/src/app/+admin/admin-registries/admin-registries.module.ts b/src/app/+admin/admin-registries/admin-registries.module.ts
index c7890e669718629c02abcdc751bce22181432709..bbeb59f0abf65b59e27c3a693769e805c28c8d79 100644
--- a/src/app/+admin/admin-registries/admin-registries.module.ts
+++ b/src/app/+admin/admin-registries/admin-registries.module.ts
@@ -5,10 +5,10 @@ import { CommonModule } from '@angular/common';
 import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
 import { RouterModule } from '@angular/router';
 import { TranslateModule } from '@ngx-translate/core';
-import { BitstreamFormatsComponent } from './bitstream-formats/bitstream-formats.component';
 import { SharedModule } from '../../shared/shared.module';
 import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component';
-import {MetadataFieldFormComponent} from './metadata-schema/metadata-field-form/metadata-field-form.component';
+import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component';
+import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.module';
 
 @NgModule({
   imports: [
@@ -16,12 +16,12 @@ import {MetadataFieldFormComponent} from './metadata-schema/metadata-field-form/
     SharedModule,
     RouterModule,
     TranslateModule,
+    BitstreamFormatsModule,
     AdminRegistriesRoutingModule
   ],
   declarations: [
     MetadataRegistryComponent,
     MetadataSchemaComponent,
-    BitstreamFormatsComponent,
     MetadataSchemaFormComponent,
     MetadataFieldFormComponent
   ],
diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..2b65b369b2960b18b6156099e6a3f303b306177e
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html
@@ -0,0 +1,11 @@
+<div class="container">
+    <div class="row">
+        <div class="col-12 mb-4">
+            <h2 id="sub-header"
+                class="border-bottom mb-2">{{ 'admin.registries.bitstream-formats.create.new' | translate }}</h2>
+
+            <ds-bitstream-format-form (updatedFormat)="createBitstreamFormat($event)"></ds-bitstream-format-form>
+
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0a10633956ca0a9f1179bc8a36ec90707ad35bda
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts
@@ -0,0 +1,106 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+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 { Router } from '@angular/router';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterStub } from '../../../../shared/testing/router-stub';
+import { of as observableOf } from 'rxjs';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
+import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
+import { ResourceType } from '../../../../core/shared/resource-type';
+import { AddBitstreamFormatComponent } from './add-bitstream-format.component';
+
+describe('AddBitstreamFormatComponent', () => {
+  let comp: AddBitstreamFormatComponent;
+  let fixture: ComponentFixture<AddBitstreamFormatComponent>;
+
+  const bitstreamFormat = new BitstreamFormat();
+  bitstreamFormat.uuid = 'test-uuid-1';
+  bitstreamFormat.id = 'test-uuid-1';
+  bitstreamFormat.shortDescription = 'Unknown';
+  bitstreamFormat.description = 'Unknown data format';
+  bitstreamFormat.mimetype = 'application/octet-stream';
+  bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
+  bitstreamFormat.internal = false;
+  bitstreamFormat.extensions = null;
+
+  let router;
+  let notificationService: NotificationsServiceStub;
+  let bitstreamFormatDataService: BitstreamFormatDataService;
+
+  const initAsync = () => {
+    router = new RouterStub();
+    notificationService = new NotificationsServiceStub();
+    bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
+      createBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success')),
+      clearBitStreamFormatRequests: observableOf(null)
+    });
+
+    TestBed.configureTestingModule({
+      imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+      declarations: [AddBitstreamFormatComponent],
+      providers: [
+        {provide: Router, useValue: router},
+        {provide: NotificationsService, useValue: notificationService},
+        {provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
+      ],
+      schemas: [CUSTOM_ELEMENTS_SCHEMA]
+    }).compileComponents();
+  };
+
+  const initBeforeEach = () => {
+    fixture = TestBed.createComponent(AddBitstreamFormatComponent);
+    comp = fixture.componentInstance;
+
+    fixture.detectChanges();
+  };
+
+  describe('createBitstreamFormat success', () => {
+    beforeEach(async(initAsync));
+    beforeEach(initBeforeEach);
+    it('should send the updated form to the service, show a notification and navigate to ', () => {
+      comp.createBitstreamFormat(bitstreamFormat);
+
+      expect(bitstreamFormatDataService.createBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
+      expect(notificationService.success).toHaveBeenCalled();
+      expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
+
+    });
+  });
+  describe('createBitstreamFormat error', () => {
+    beforeEach(async(() => {
+      router = new RouterStub();
+      notificationService = new NotificationsServiceStub();
+      bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
+        createBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request')),
+        clearBitStreamFormatRequests: observableOf(null)
+      });
+
+      TestBed.configureTestingModule({
+        imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+        declarations: [AddBitstreamFormatComponent],
+        providers: [
+          {provide: Router, useValue: router},
+          {provide: NotificationsService, useValue: notificationService},
+          {provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
+        ],
+        schemas: [CUSTOM_ELEMENTS_SCHEMA]
+      }).compileComponents();
+    }));
+    beforeEach(initBeforeEach);
+    it('should send the updated form to the service, show a notification and navigate to ', () => {
+      comp.createBitstreamFormat(bitstreamFormat);
+
+      expect(bitstreamFormatDataService.createBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
+      expect(notificationService.error).toHaveBeenCalled();
+      expect(router.navigate).not.toHaveBeenCalled();
+
+    });
+  });
+});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9712be70cade4539d58968e3dbc19af48f9e0e7d
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts
@@ -0,0 +1,49 @@
+import { take } from 'rxjs/operators';
+import { Router } from '@angular/router';
+import { Component } from '@angular/core';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
+import { TranslateService } from '@ngx-translate/core';
+
+/**
+ * This component renders the page to create a new bitstream format.
+ */
+@Component({
+  selector: 'ds-add-bitstream-format',
+  templateUrl: './add-bitstream-format.component.html',
+})
+export class AddBitstreamFormatComponent {
+
+  constructor(
+    private router: Router,
+    private notificationService: NotificationsService,
+    private translateService: TranslateService,
+    private bitstreamFormatDataService: BitstreamFormatDataService,
+  ) {
+  }
+
+  /**
+   * Creates a new bitstream format based on the provided bitstream format emitted by the form.
+   * When successful, a success notification will be shown and the user will be navigated back to the overview page.
+   * When failed, an error  notification will be shown.
+   * @param bitstreamFormat
+   */
+  createBitstreamFormat(bitstreamFormat: BitstreamFormat) {
+    this.bitstreamFormatDataService.createBitstreamFormat(bitstreamFormat).pipe(take(1)
+    ).subscribe((response: RestResponse) => {
+        if (response.isSuccessful) {
+          this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.create.success.head'),
+            this.translateService.get('admin.registries.bitstream-formats.create.success.content'));
+          this.router.navigate([getBitstreamFormatsModulePath()]);
+          this.bitstreamFormatDataService.clearBitStreamFormatRequests().subscribe();
+        } else {
+          this.notificationService.error(this.translateService.get('admin.registries.bitstream-formats.create.failure.head'),
+            this.translateService.get('admin.registries.bitstream-formats.create.failure.content'));
+        }
+      }
+    );
+  }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.actions.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.actions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..58b0686dfd7809abd201cd9659b992eb5e52e07e
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.actions.ts
@@ -0,0 +1,64 @@
+import { Action } from '@ngrx/store';
+import { type } from '../../../shared/ngrx/type';
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+
+/**
+ * For each action type in an action group, make a simple
+ * enum object for all of this group's action types.
+ *
+ * The 'type' utility function coerces strings into string
+ * literal types and runs a simple check to guarantee all
+ * action types in the application are unique.
+ */
+export const BitstreamFormatsRegistryActionTypes = {
+
+  SELECT_FORMAT: type('dspace/bitstream-formats-registry/SELECT_FORMAT'),
+  DESELECT_FORMAT: type('dspace/bitstream-formats-registry/DESELECT_FORMAT'),
+  DESELECT_ALL_FORMAT: type('dspace/bitstream-formats-registry/DESELECT_ALL_FORMAT')
+};
+
+/* tslint:disable:max-classes-per-file */
+/**
+ * Used to select a single bitstream format in the bitstream format registry
+ */
+export class BitstreamFormatsRegistrySelectAction implements Action {
+  type = BitstreamFormatsRegistryActionTypes.SELECT_FORMAT;
+
+  bitstreamFormat: BitstreamFormat;
+
+  constructor(bitstreamFormat: BitstreamFormat) {
+    this.bitstreamFormat = bitstreamFormat;
+  }
+}
+
+/**
+ * Used to deselect a single bitstream format in the bitstream format registry
+ */
+export class BitstreamFormatsRegistryDeselectAction implements Action {
+  type = BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT;
+
+  bitstreamFormat: BitstreamFormat;
+
+  constructor(bitstreamFormat: BitstreamFormat) {
+    this.bitstreamFormat = bitstreamFormat;
+  }
+}
+
+/**
+ * Used to deselect all bitstream formats in the bitstream format registry
+ */
+export class BitstreamFormatsRegistryDeselectAllAction implements Action {
+  type = BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT;
+}
+
+/* tslint:enable:max-classes-per-file */
+
+/**
+ * Export a type alias of all actions in this action group
+ * so that reducers can easily compose action types
+ * These are all the actions to perform on the bitstream format registry state
+ */
+export type BitstreamFormatsRegistryAction
+  = BitstreamFormatsRegistrySelectAction
+  | BitstreamFormatsRegistryDeselectAction
+  | BitstreamFormatsRegistryDeselectAllAction
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..76576afc7a134db4502b5febb6946ce67b502e83
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.spec.ts
@@ -0,0 +1,83 @@
+import { Action } from '@ngrx/store';
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+import { bitstreamFormatReducer, BitstreamFormatRegistryState } from './bitstream-format.reducers';
+import {
+  BitstreamFormatsRegistryDeselectAction,
+  BitstreamFormatsRegistryDeselectAllAction,
+  BitstreamFormatsRegistrySelectAction
+} from './bitstream-format.actions';
+
+const bitstreamFormat1: BitstreamFormat = new BitstreamFormat();
+bitstreamFormat1.id = 'test-uuid-1';
+bitstreamFormat1.shortDescription = 'test-short-1';
+
+const bitstreamFormat2: BitstreamFormat = new BitstreamFormat();
+bitstreamFormat2.id = 'test-uuid-2';
+bitstreamFormat2.shortDescription = 'test-short-2';
+
+const initialState: BitstreamFormatRegistryState = {
+  selectedBitstreamFormats: []
+};
+
+const bitstream1SelectedState: BitstreamFormatRegistryState = {
+  selectedBitstreamFormats: [bitstreamFormat1]
+};
+
+const bitstream1and2SelectedState: BitstreamFormatRegistryState = {
+  selectedBitstreamFormats: [bitstreamFormat1, bitstreamFormat2]
+};
+
+describe('BitstreamFormatReducer', () => {
+  describe('BitstreamFormatsRegistryActionTypes.SELECT_FORMAT', () => {
+    it('should add the format to the list of selected formats when initial list is empty', () => {
+      const state = initialState;
+      const action = new BitstreamFormatsRegistrySelectAction(bitstreamFormat1);
+      const newState = bitstreamFormatReducer(state, action);
+
+      expect(newState).toEqual(bitstream1SelectedState);
+    });
+    it('should add the format to the list of selected formats when formats are already present', () => {
+      const state = bitstream1SelectedState;
+      const action = new BitstreamFormatsRegistrySelectAction(bitstreamFormat2);
+      const newState = bitstreamFormatReducer(state, action);
+
+      expect(newState).toEqual(bitstream1and2SelectedState);
+    });
+  });
+  describe('BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT', () => {
+    it('should deselect a format', () => {
+      const state = bitstream1and2SelectedState;
+      const action = new BitstreamFormatsRegistryDeselectAction(bitstreamFormat2);
+      const newState = bitstreamFormatReducer(state, action);
+
+      expect(newState).toEqual(bitstream1SelectedState);
+    });
+  });
+  describe('BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT', () => {
+    it('should deselect all formats', () => {
+      const state = bitstream1and2SelectedState;
+      const action = new BitstreamFormatsRegistryDeselectAllAction();
+      const newState = bitstreamFormatReducer(state, action);
+
+      expect(newState).toEqual(initialState);
+    });
+  });
+  describe('Invalid action', () => {
+    it('should return the current state', () => {
+      const state = initialState;
+      const action = new NullAction();
+
+      const newState = bitstreamFormatReducer(state, action);
+
+      expect(newState).toEqual(state);
+    });
+  });
+});
+
+class NullAction implements Action {
+  type = null;
+
+  constructor() {
+    // empty constructor
+  }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..41880bf16cd37e29a500184832b74eb071e05295
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.ts
@@ -0,0 +1,55 @@
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+import {
+  BitstreamFormatsRegistryAction,
+  BitstreamFormatsRegistryActionTypes,
+  BitstreamFormatsRegistryDeselectAction,
+  BitstreamFormatsRegistrySelectAction
+} from './bitstream-format.actions';
+
+/**
+ * The bitstream format registry state.
+ * @interface BitstreamFormatRegistryState
+ */
+export interface BitstreamFormatRegistryState {
+  selectedBitstreamFormats: BitstreamFormat[];
+}
+
+/**
+ * The initial state.
+ */
+const initialState: BitstreamFormatRegistryState = {
+  selectedBitstreamFormats: [],
+};
+
+/**
+ * Reducer that handles BitstreamFormatsRegistryActions to modify the bitstream format registry state
+ * @param state   The current BitstreamFormatRegistryState
+ * @param action  The BitstreamFormatsRegistryAction to perform on the state
+ */
+export function bitstreamFormatReducer(state = initialState, action: BitstreamFormatsRegistryAction): BitstreamFormatRegistryState {
+
+  switch (action.type) {
+
+    case BitstreamFormatsRegistryActionTypes.SELECT_FORMAT: {
+      return Object.assign({}, state, {
+        selectedBitstreamFormats: [...state.selectedBitstreamFormats, (action as BitstreamFormatsRegistrySelectAction).bitstreamFormat]
+      });
+    }
+
+    case BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT: {
+      return Object.assign({}, state, {
+        selectedBitstreamFormats: state.selectedBitstreamFormats.filter(
+          (selectedBitstreamFormats) => selectedBitstreamFormats !== (action as BitstreamFormatsRegistryDeselectAction).bitstreamFormat
+        )
+      });
+    }
+
+    case BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT: {
+      return Object.assign({}, state, {
+        selectedBitstreamFormats: []
+      });
+    }
+    default:
+      return state;
+  }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..67f6aa373e5f50545369ea5032310d422e568ffa
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts
@@ -0,0 +1,37 @@
+import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { BitstreamFormatsResolver } from './bitstream-formats.resolver';
+import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
+import { BitstreamFormatsComponent } from './bitstream-formats.component';
+import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
+
+const BITSTREAMFORMAT_EDIT_PATH = ':id/edit';
+const BITSTREAMFORMAT_ADD_PATH = 'add';
+
+@NgModule({
+  imports: [
+    RouterModule.forChild([
+      {
+        path: '',
+        component: BitstreamFormatsComponent
+      },
+      {
+        path: BITSTREAMFORMAT_ADD_PATH,
+        component: AddBitstreamFormatComponent,
+      },
+      {
+        path: BITSTREAMFORMAT_EDIT_PATH,
+        component: EditBitstreamFormatComponent,
+        resolve: {
+          bitstreamFormat: BitstreamFormatsResolver
+        }
+      },
+    ])
+  ],
+  providers: [
+    BitstreamFormatsResolver,
+  ]
+})
+export class BitstreamFormatsRoutingModule {
+
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
index 1ac547653ff972713028e67f3a832cab84a5342e..e5cf7cf5ecc338d4c76057e207455f795623c1df 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
@@ -2,13 +2,15 @@
   <div class="bitstream-formats row">
     <div class="col-12">
 
-      <h2 id="header" class="border-bottom pb-2">{{'admin.registries.bitstream-formats.head' | translate}}</h2>
+      <h2 id="header" class="border-bottom pb-2 ">{{'admin.registries.bitstream-formats.head' | translate}}</h2>
+
+      <p id="description">{{'admin.registries.bitstream-formats.description' | translate}}</p>
+      <p id="create-new" class="mb-2"><a [routerLink]="'add'" class="btn btn-success">{{'admin.registries.bitstream-formats.create.new' | translate}}</a></p>
 
-      <p id="description" class="pb-2">{{'admin.registries.bitstream-formats.description' | translate}}</p>
 
       <ds-pagination
         *ngIf="(bitstreamFormats | async)?.payload?.totalElements > 0"
-        [paginationOptions]="config"
+        [paginationOptions]="pageConfig"
         [pageInfoState]="(bitstreamFormats | async)?.payload"
         [collectionSize]="(bitstreamFormats | async)?.payload?.totalElements"
         [hideGear]="true"
@@ -18,25 +20,38 @@
           <table id="formats" class="table table-striped table-hover">
             <thead>
               <tr>
-                <th scope="col">{{'admin.registries.bitstream-formats.formats.table.name' | translate}}</th>
-                <th scope="col">{{'admin.registries.bitstream-formats.formats.table.mimetype' | translate}}</th>
-                <th scope="col">{{'admin.registries.bitstream-formats.formats.table.supportLevel.head' | translate}}</th>
+                <th scope="col"></th>
+                <th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
+                <th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
+                <th scope="col">{{'admin.registries.bitstream-formats.table.supportLevel.head' | translate}}</th>
               </tr>
             </thead>
             <tbody>
               <tr *ngFor="let bitstreamFormat of (bitstreamFormats | async)?.payload?.page">
-                <td>{{bitstreamFormat.shortDescription}}</td>
-                <td>{{bitstreamFormat.mimetype}} <span *ngIf="bitstreamFormat.internal">({{'admin.registries.bitstream-formats.formats.table.internal' | translate}})</span></td>
-                <td>{{'admin.registries.bitstream-formats.formats.table.supportLevel.'+bitstreamFormat.supportLevel | translate}}</td>
+                <td>
+                  <label>
+                    <input type="checkbox"
+                           [checked]="isSelected(bitstreamFormat) | async"
+                           (change)="selectBitStreamFormat(bitstreamFormat, $event)"
+                    >
+                  </label>
+                </td>
+                <td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.shortDescription}}</a></td>
+                <td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.mimetype}} <span *ngIf="bitstreamFormat.internal">({{'admin.registries.bitstream-formats.table.internal' | translate}})</span></a></td>
+                <td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{'admin.registries.bitstream-formats.table.supportLevel.'+bitstreamFormat.supportLevel | translate}}</a></td>
               </tr>
             </tbody>
           </table>
         </div>
       </ds-pagination>
       <div *ngIf="(bitstreamFormats | async)?.payload?.totalElements == 0" class="alert alert-info" role="alert">
-        {{'admin.registries.bitstream-formats.formats.no-items' | translate}}
+        {{'admin.registries.bitstream-formats.no-items' | translate}}
       </div>
 
+      <div>
+        <button *ngIf="(bitstreamFormats | async)?.payload?.page?.length > 0" class="btn btn-primary deselect" (click)="deselectAll()">{{'admin.registries.bitstream-formats.table.deselect-all' | translate}}</button>
+        <button *ngIf="(bitstreamFormats | async)?.payload?.page?.length > 0" type="submit" class="btn btn-danger float-right" (click)="deleteFormats()">{{'admin.registries.bitstream-formats.table.delete' | translate}}</button>
+      </div>
     </div>
   </div>
 </div>
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
index 3a680c906b7dbd3c8f2d528df06cb623ea4d57ca..e672dc82ea5ce3ed3602eb9b249144d55edbee87 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
@@ -1,6 +1,5 @@
 import { BitstreamFormatsComponent } from './bitstream-formats.component';
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { RegistryService } from '../../../core/registry/registry.service';
 import { of as observableOf } from 'rxjs';
 import { RemoteData } from '../../../core/data/remote-data';
 import { PaginatedList } from '../../../core/data/paginated-list';
@@ -13,86 +12,278 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
 import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
 import { HostWindowService } from '../../../shared/host-window.service';
 import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
-import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
+import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level';
+import { cold, getTestScheduler, hot } from 'jasmine-marbles';
+import { TestScheduler } from 'rxjs/testing';
 
 describe('BitstreamFormatsComponent', () => {
   let comp: BitstreamFormatsComponent;
   let fixture: ComponentFixture<BitstreamFormatsComponent>;
-  let registryService: RegistryService;
-  const mockFormatsList = [
-    {
-      shortDescription: 'Unknown',
-      description: 'Unknown data format',
-      mimetype: 'application/octet-stream',
-      supportLevel: 0,
-      internal: false,
-      extensions: null
-    },
-    {
-      shortDescription: 'License',
-      description: 'Item-specific license agreed upon to submission',
-      mimetype: 'text/plain; charset=utf-8',
-      supportLevel: 1,
-      internal: true,
-      extensions: null
-    },
-    {
-      shortDescription: 'CC License',
-      description: 'Item-specific Creative Commons license agreed upon to submission',
-      mimetype: 'text/html; charset=utf-8',
-      supportLevel: 2,
-      internal: true,
-      extensions: null
-    },
-    {
-      shortDescription: 'Adobe PDF',
-      description: 'Adobe Portable Document Format',
-      mimetype: 'application/pdf',
-      supportLevel: 0,
-      internal: false,
-      extensions: null
-    }
+  let bitstreamFormatService;
+  let scheduler: TestScheduler;
+  let notificationsServiceStub;
+
+  const bitstreamFormat1 = new BitstreamFormat();
+  bitstreamFormat1.uuid = 'test-uuid-1';
+  bitstreamFormat1.id = 'test-uuid-1';
+  bitstreamFormat1.shortDescription = 'Unknown';
+  bitstreamFormat1.description = 'Unknown data format';
+  bitstreamFormat1.mimetype = 'application/octet-stream';
+  bitstreamFormat1.supportLevel = BitstreamFormatSupportLevel.Unknown;
+  bitstreamFormat1.internal = false;
+  bitstreamFormat1.extensions = null;
+
+  const bitstreamFormat2 = new BitstreamFormat();
+  bitstreamFormat2.uuid = 'test-uuid-2';
+  bitstreamFormat2.id = 'test-uuid-2';
+  bitstreamFormat2.shortDescription = 'License';
+  bitstreamFormat2.description = 'Item-specific license agreed upon to submission';
+  bitstreamFormat2.mimetype = 'text/plain; charset=utf-8';
+  bitstreamFormat2.supportLevel = BitstreamFormatSupportLevel.Known;
+  bitstreamFormat2.internal = true;
+  bitstreamFormat2.extensions = null;
+
+  const bitstreamFormat3 = new BitstreamFormat();
+  bitstreamFormat3.uuid = 'test-uuid-3';
+  bitstreamFormat3.id = 'test-uuid-3';
+  bitstreamFormat3.shortDescription = 'CC License';
+  bitstreamFormat3.description = 'Item-specific Creative Commons license agreed upon to submission';
+  bitstreamFormat3.mimetype = 'text/html; charset=utf-8';
+  bitstreamFormat3.supportLevel = BitstreamFormatSupportLevel.Supported;
+  bitstreamFormat3.internal = true;
+  bitstreamFormat3.extensions = null;
+
+  const bitstreamFormat4 = new BitstreamFormat();
+  bitstreamFormat4.uuid = 'test-uuid-4';
+  bitstreamFormat4.id = 'test-uuid-4';
+  bitstreamFormat4.shortDescription = 'Adobe PDF';
+  bitstreamFormat4.description = 'Adobe Portable Document Format';
+  bitstreamFormat4.mimetype = 'application/pdf';
+  bitstreamFormat4.supportLevel = BitstreamFormatSupportLevel.Unknown;
+  bitstreamFormat4.internal = false;
+  bitstreamFormat4.extensions = null;
+
+  const mockFormatsList: BitstreamFormat[] = [
+    bitstreamFormat1,
+    bitstreamFormat2,
+    bitstreamFormat3,
+    bitstreamFormat4
   ];
-  const mockFormats = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockFormatsList));
-  const registryServiceStub = {
-    getBitstreamFormats: () => mockFormats
-  };
+  const mockFormatsRD = new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList));
+
+  const initAsync = () => {
+    notificationsServiceStub = new NotificationsServiceStub();
+
+    scheduler = getTestScheduler();
+
+    bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
+      findAll: observableOf(mockFormatsRD),
+      find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
+      getSelectedBitstreamFormats: hot('a', {a: mockFormatsList}),
+      selectBitstreamFormat: {},
+      deselectBitstreamFormat: {},
+      deselectAllBitstreamFormats: {},
+      delete: observableOf(true),
+      clearBitStreamFormatRequests: observableOf('cleared')
+    });
 
-  beforeEach(async(() => {
     TestBed.configureTestingModule({
       imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
       declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
       providers: [
-        { provide: RegistryService, useValue: registryServiceStub },
-        { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }
+        {provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
+        {provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
+        {provide: NotificationsService, useValue: notificationsServiceStub}
       ]
     }).compileComponents();
-  }));
+  };
 
-  beforeEach(() => {
+  const initBeforeEach = () => {
     fixture = TestBed.createComponent(BitstreamFormatsComponent);
     comp = fixture.componentInstance;
     fixture.detectChanges();
-    registryService = (comp as any).service;
+  };
+
+  describe('Bitstream format page content', () => {
+    beforeEach(async(initAsync));
+    beforeEach(initBeforeEach);
+
+    it('should contain four formats', () => {
+      const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement;
+      expect(tbody.children.length).toBe(4);
+    });
+
+    it('should contain the correct formats', () => {
+      const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(2)')).nativeElement;
+      expect(unknownName.textContent).toBe('Unknown');
+
+      const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(2)')).nativeElement;
+      expect(licenseName.textContent).toBe('License');
+
+      const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(2)')).nativeElement;
+      expect(ccLicenseName.textContent).toBe('CC License');
+
+      const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(2)')).nativeElement;
+      expect(adobeName.textContent).toBe('Adobe PDF');
+    });
+  });
+
+  describe('selectBitStreamFormat', () => {
+    beforeEach(async(initAsync));
+    beforeEach(initBeforeEach);
+    it('should select a bitstreamFormat if it was selected in the event', () => {
+      const event = {target: {checked: true}};
+
+      comp.selectBitStreamFormat(bitstreamFormat1, event);
+
+      expect(bitstreamFormatService.selectBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat1);
+    });
+    it('should deselect a bitstreamFormat if it is deselected in the event', () => {
+      const event = {target: {checked: false}};
+
+      comp.selectBitStreamFormat(bitstreamFormat1, event);
+
+      expect(bitstreamFormatService.deselectBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat1);
+    });
+    it('should be called when a user clicks a checkbox', () => {
+      spyOn(comp, 'selectBitStreamFormat');
+      const unknownFormat = fixture.debugElement.query(By.css('#formats tr:nth-child(1) input'));
+
+      const event = {target: {checked: true}};
+      unknownFormat.triggerEventHandler('change', event);
+
+      expect(comp.selectBitStreamFormat).toHaveBeenCalledWith(bitstreamFormat1, event);
+    });
   });
 
-  it('should contain four formats', () => {
-    const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement;
-    expect(tbody.children.length).toBe(4);
+  describe('isSelected', () => {
+    beforeEach(async(initAsync));
+    beforeEach(initBeforeEach);
+    it('should return an observable of true if the provided bistream is in the list returned by the service', () => {
+      const result = comp.isSelected(bitstreamFormat1);
+
+      expect(result).toBeObservable(cold('b', {b: true}));
+    });
+    it('should return an observable of false if the provided bistream is not in the list returned by the service', () => {
+      const format = new BitstreamFormat();
+      format.uuid = 'new';
+
+      const result = comp.isSelected(format);
+
+      expect(result).toBeObservable(cold('b', {b: false}));
+    });
+  });
+
+  describe('deselectAll', () => {
+    beforeEach(async(initAsync));
+    beforeEach(initBeforeEach);
+    it('should deselect all bitstreamFormats', () => {
+      comp.deselectAll();
+      expect(bitstreamFormatService.deselectAllBitstreamFormats).toHaveBeenCalled();
+    });
+
+    it('should be called when the deselect all button is clicked', () => {
+      spyOn(comp, 'deselectAll');
+      const deselectAllButton = fixture.debugElement.query(By.css('button.deselect'));
+      deselectAllButton.triggerEventHandler('click', null);
+
+      expect(comp.deselectAll).toHaveBeenCalled();
+
+    });
   });
 
-  it('should contain the correct formats', () => {
-    const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(1)')).nativeElement;
-    expect(unknownName.textContent).toBe('Unknown');
+  describe('deleteFormats success', () => {
+    beforeEach(async(() => {
+        notificationsServiceStub = new NotificationsServiceStub();
 
-    const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(1)')).nativeElement;
-    expect(licenseName.textContent).toBe('License');
+        scheduler = getTestScheduler();
 
-    const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(1)')).nativeElement;
-    expect(ccLicenseName.textContent).toBe('CC License');
+        bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
+          findAll: observableOf(mockFormatsRD),
+          find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
+          getSelectedBitstreamFormats: observableOf(mockFormatsList),
+          selectBitstreamFormat: {},
+          deselectBitstreamFormat: {},
+          deselectAllBitstreamFormats: {},
+          delete: observableOf(true),
+          clearBitStreamFormatRequests: observableOf('cleared')
+        });
 
-    const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(1)')).nativeElement;
-    expect(adobeName.textContent).toBe('Adobe PDF');
+        TestBed.configureTestingModule({
+          imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+          declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
+          providers: [
+            {provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
+            {provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
+            {provide: NotificationsService, useValue: notificationsServiceStub}
+          ]
+        }).compileComponents();
+      }
+    ));
+
+    beforeEach(initBeforeEach);
+    it('should clear bitstream formats  ', () => {
+      comp.deleteFormats();
+
+      expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
+      expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat1);
+      expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat2);
+      expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat3);
+      expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat4);
+
+      expect(notificationsServiceStub.success).toHaveBeenCalledWith('admin.registries.bitstream-formats.delete.success.head',
+        'admin.registries.bitstream-formats.delete.success.amount');
+      expect(notificationsServiceStub.error).not.toHaveBeenCalled();
+
+    });
   });
 
+  describe('deleteFormats error', () => {
+    beforeEach(async(() => {
+        notificationsServiceStub = new NotificationsServiceStub();
+
+        scheduler = getTestScheduler();
+
+        bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
+          findAll: observableOf(mockFormatsRD),
+          find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
+          getSelectedBitstreamFormats: observableOf(mockFormatsList),
+          selectBitstreamFormat: {},
+          deselectBitstreamFormat: {},
+          deselectAllBitstreamFormats: {},
+          delete: observableOf(false),
+          clearBitStreamFormatRequests: observableOf('cleared')
+        });
+
+        TestBed.configureTestingModule({
+          imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+          declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
+          providers: [
+            {provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
+            {provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
+            {provide: NotificationsService, useValue: notificationsServiceStub}
+          ]
+        }).compileComponents();
+      }
+    ));
+
+    beforeEach(initBeforeEach);
+    it('should clear bitstream formats  ', () => {
+      comp.deleteFormats();
+
+      expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
+      expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat1);
+      expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat2);
+      expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat3);
+      expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat4);
+
+      expect(notificationsServiceStub.error).toHaveBeenCalledWith('admin.registries.bitstream-formats.delete.failure.head',
+        'admin.registries.bitstream-formats.delete.failure.amount');
+      expect(notificationsServiceStub.success).not.toHaveBeenCalled();
+    });
+  });
 });
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
index bc0cbb8da61fdd838e06f7292b65fc29f7934659..cb7aa1ef91d7e88a39177f3bf3e1df0f4d14d518 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
@@ -1,10 +1,16 @@
-import { Component } from '@angular/core';
-import { RegistryService } from '../../../core/registry/registry.service';
-import { Observable } from 'rxjs';
+import { Component, OnInit } from '@angular/core';
+import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs';
 import { RemoteData } from '../../../core/data/remote-data';
 import { PaginatedList } from '../../../core/data/paginated-list';
-import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model';
 import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
+import { FindAllOptions } from '../../../core/data/request.models';
+import { map, switchMap, take } from 'rxjs/operators';
+import { hasValue } from '../../../shared/empty.util';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { Router } from '@angular/router';
+import { TranslateService } from '@ngx-translate/core';
 
 /**
  * This component renders a list of bitstream formats
@@ -13,24 +19,125 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
   selector: 'ds-bitstream-formats',
   templateUrl: './bitstream-formats.component.html'
 })
-export class BitstreamFormatsComponent {
+export class BitstreamFormatsComponent implements OnInit {
 
   /**
    * A paginated list of bitstream formats to be shown on the page
    */
   bitstreamFormats: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
 
+  /**
+   * A BehaviourSubject that keeps track of the pageState used to update the currently displayed bitstreamFormats
+   */
+  pageState: BehaviorSubject<string>;
+
+  /**
+   * The current pagination configuration for the page used by the FindAll method
+   * Currently simply renders all bitstream formats
+   */
+  config: FindAllOptions = Object.assign(new FindAllOptions(), {
+    elementsPerPage: 20
+  });
+
   /**
    * The current pagination configuration for the page
    * Currently simply renders all bitstream formats
    */
-  config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
+  pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
     id: 'registry-bitstreamformats-pagination',
-    pageSize: 10000
+    pageSize: 20
   });
 
-  constructor(private registryService: RegistryService) {
-    this.updateFormats();
+  constructor(private notificationsService: NotificationsService,
+              private router: Router,
+              private translateService: TranslateService,
+              private bitstreamFormatService: BitstreamFormatDataService) {
+  }
+
+  /**
+   * Deletes the currently selected formats from the registry and updates the presented list
+   */
+  deleteFormats() {
+    this.bitstreamFormatService.clearBitStreamFormatRequests().subscribe();
+    this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(take(1)).subscribe(
+      (formats) => {
+        const tasks$ = [];
+        for (const format of formats) {
+          if (hasValue(format.id)) {
+            tasks$.push(this.bitstreamFormatService.delete(format));
+          }
+        }
+        zip(...tasks$).subscribe((results: boolean[]) => {
+          const successResponses = results.filter((result: boolean) => result);
+          const failedResponses = results.filter((result: boolean) => !result);
+          if (successResponses.length > 0) {
+            this.showNotification(true, successResponses.length);
+          }
+          if (failedResponses.length > 0) {
+            this.showNotification(false, failedResponses.length);
+          }
+
+          this.deselectAll();
+
+          this.router.navigate([], {
+            queryParams: Object.assign({}, { page: 1 }),
+            queryParamsHandling: 'merge'
+          });        });
+      }
+    );
+  }
+
+  /**
+   * Deselects all selecetd bitstream formats
+   */
+  deselectAll() {
+    this.bitstreamFormatService.deselectAllBitstreamFormats();
+  }
+
+  /**
+   * Checks whether a given bitstream format is selected in the list (checkbox)
+   * @param bitstreamFormat
+   */
+  isSelected(bitstreamFormat: BitstreamFormat): Observable<boolean> {
+    return this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(
+      map((bitstreamFormats: BitstreamFormat[]) => {
+        return bitstreamFormats.find((selectedFormat) => selectedFormat.id === bitstreamFormat.id) != null;
+      })
+    );
+  }
+
+  /**
+   * Selects or deselects a bitstream format based on the checkbox state
+   * @param bitstreamFormat
+   * @param event
+   */
+  selectBitStreamFormat(bitstreamFormat: BitstreamFormat, event) {
+    event.target.checked ?
+      this.bitstreamFormatService.selectBitstreamFormat(bitstreamFormat) :
+      this.bitstreamFormatService.deselectBitstreamFormat(bitstreamFormat);
+  }
+
+  /**
+   * Show notifications for an amount of deleted bitstream formats
+   * @param success   Whether or not the notification should be a success message (error message when false)
+   * @param amount    The amount of deleted bitstream formats
+   */
+  private showNotification(success: boolean, amount: number) {
+    const prefix = 'admin.registries.bitstream-formats.delete';
+    const suffix = success ? 'success' : 'failure';
+
+    const messages = observableCombineLatest(
+      this.translateService.get(`${prefix}.${suffix}.head`),
+      this.translateService.get(`${prefix}.${suffix}.amount`, {amount: amount})
+    );
+    messages.subscribe(([head, content]) => {
+
+      if (success) {
+        this.notificationsService.success(head, content);
+      } else {
+        this.notificationsService.error(head, content);
+      }
+    });
   }
 
   /**
@@ -38,14 +145,26 @@ export class BitstreamFormatsComponent {
    * @param event The page change event
    */
   onPageChange(event) {
-    this.config.currentPage = event;
-    this.updateFormats();
+    this.config = Object.assign(new FindAllOptions(), this.config, {
+      currentPage: event,
+    });
+    this.pageConfig.currentPage = event;
+    this.pageState.next('pageChange');
+  }
+
+  ngOnInit(): void {
+    this.pageState = new BehaviorSubject('init');
+    this.bitstreamFormats = this.pageState.pipe(
+      switchMap(() => {
+        return this.updateFormats()
+          ;
+      }));
   }
 
   /**
-   * Method to update the bitstream formats that are shown
+   * Finds all formats based on the current config
    */
   private updateFormats() {
-    this.bitstreamFormats = this.registryService.getBitstreamFormats(this.config);
+    return this.bitstreamFormatService.findAll(this.config);
   }
 }
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0800c501692435f6486a8654a6c89bf7352144df
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts
@@ -0,0 +1,30 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { TranslateModule } from '@ngx-translate/core';
+import { BitstreamFormatsComponent } from './bitstream-formats.component';
+import { SharedModule } from '../../../shared/shared.module';
+import { FormatFormComponent } from './format-form/format-form.component';
+import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
+import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module';
+import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    SharedModule,
+    RouterModule,
+    TranslateModule,
+    BitstreamFormatsRoutingModule
+  ],
+  declarations: [
+    BitstreamFormatsComponent,
+    EditBitstreamFormatComponent,
+    AddBitstreamFormatComponent,
+    FormatFormComponent
+  ],
+  entryComponents: []
+})
+export class BitstreamFormatsModule {
+
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f6eef741fd3719c23fefcd49b3f0f832f53e0e0f
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts
@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
+import { Observable } from 'rxjs';
+import { find } from 'rxjs/operators';
+import { RemoteData } from '../../../core/data/remote-data';
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
+import { hasValue } from '../../../shared/empty.util';
+
+/**
+ * This class represents a resolver that requests a specific bitstreamFormat before the route is activated
+ */
+@Injectable()
+export class BitstreamFormatsResolver implements Resolve<RemoteData<BitstreamFormat>> {
+  constructor(private bitstreamFormatDataService: BitstreamFormatDataService) {
+  }
+
+  /**
+   * Method for resolving an bitstreamFormat based on the parameters in the current route
+   * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
+   * @param {RouterStateSnapshot} state The current RouterStateSnapshot
+   * @returns Observable<<RemoteData<BitstreamFormat>> Emits the found bitstreamFormat based on the parameters in the current route,
+   * or an error if something went wrong
+   */
+  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<BitstreamFormat>> {
+    return this.bitstreamFormatDataService.findById(route.params.id)
+      .pipe(
+        find((RD) => hasValue(RD.error) || RD.hasSucceeded),
+      );
+  }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..f57ec9cd382a117e1b97fe95db16d7b03a26db81
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html
@@ -0,0 +1,11 @@
+<div class="container">
+    <div class="row">
+        <div class="col-12 mb-4">
+            <h2 id="sub-header"
+                class="border-bottom mb-2">{{'admin.registries.bitstream-formats.edit.head' | translate:{format: (bitstreamFormatRD$ | async)?.payload.shortDescription} }}</h2>
+
+             <ds-bitstream-format-form [bitstreamFormat]="(bitstreamFormatRD$ | async)?.payload" (updatedFormat)="updateFormat($event)"></ds-bitstream-format-form>
+
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cfa93a15a84c80d74e26a4fb1b895e7c7c7ba58c
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts
@@ -0,0 +1,123 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+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 { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterStub } from '../../../../shared/testing/router-stub';
+import { of as observableOf } from 'rxjs';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { EditBitstreamFormatComponent } from './edit-bitstream-format.component';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
+import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
+import { ResourceType } from '../../../../core/shared/resource-type';
+
+describe('EditBitstreamFormatComponent', () => {
+  let comp: EditBitstreamFormatComponent;
+  let fixture: ComponentFixture<EditBitstreamFormatComponent>;
+
+  const bitstreamFormat = new BitstreamFormat();
+  bitstreamFormat.uuid = 'test-uuid-1';
+  bitstreamFormat.id = 'test-uuid-1';
+  bitstreamFormat.shortDescription = 'Unknown';
+  bitstreamFormat.description = 'Unknown data format';
+  bitstreamFormat.mimetype = 'application/octet-stream';
+  bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
+  bitstreamFormat.internal = false;
+  bitstreamFormat.extensions = null;
+
+  const routeStub = {
+    data: observableOf({
+      bitstreamFormat: new RemoteData(false, false, true, null, bitstreamFormat)
+    })
+  };
+
+  let router;
+  let notificationService: NotificationsServiceStub;
+  let bitstreamFormatDataService: BitstreamFormatDataService;
+
+  const initAsync = () => {
+    router =  new RouterStub();
+    notificationService = new NotificationsServiceStub();
+    bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
+      updateBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success'))
+    });
+
+    TestBed.configureTestingModule({
+      imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+      declarations: [EditBitstreamFormatComponent],
+      providers: [
+        {provide: ActivatedRoute, useValue: routeStub},
+        {provide: Router, useValue: router},
+        {provide: NotificationsService, useValue: notificationService},
+        {provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
+      ],
+      schemas: [CUSTOM_ELEMENTS_SCHEMA]
+    }).compileComponents();
+  };
+
+  const initBeforeEach = () => {
+    fixture = TestBed.createComponent(EditBitstreamFormatComponent);
+    comp = fixture.componentInstance;
+
+    fixture.detectChanges();
+  };
+
+  describe('init', () => {
+    beforeEach(async(initAsync));
+    beforeEach(initBeforeEach);
+    it('should initialise the bitstreamFormat based on the route', () => {
+
+      comp.bitstreamFormatRD$.subscribe((format: RemoteData<BitstreamFormat>) => {
+        expect(format).toEqual(new RemoteData(false, false, true, null, bitstreamFormat));
+      });
+    });
+  });
+  describe('updateFormat success', () => {
+    beforeEach(async(initAsync));
+    beforeEach(initBeforeEach);
+    it('should send the updated form to the service, show a notification and navigate to ', () => {
+      comp.updateFormat(bitstreamFormat);
+
+      expect(bitstreamFormatDataService.updateBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
+      expect(notificationService.success).toHaveBeenCalled();
+      expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
+
+    });
+  });
+  describe('updateFormat error', () => {
+    beforeEach(async( () => {
+      router =  new RouterStub();
+      notificationService = new NotificationsServiceStub();
+      bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
+        updateBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request'))
+      });
+
+      TestBed.configureTestingModule({
+        imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+        declarations: [EditBitstreamFormatComponent],
+        providers: [
+          {provide: ActivatedRoute, useValue: routeStub},
+          {provide: Router, useValue: router},
+          {provide: NotificationsService, useValue: notificationService},
+          {provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
+        ],
+        schemas: [CUSTOM_ELEMENTS_SCHEMA]
+      }).compileComponents();
+    }));
+    beforeEach(initBeforeEach);
+    it('should send the updated form to the service, show a notification and navigate to ', () => {
+      comp.updateFormat(bitstreamFormat);
+
+      expect(bitstreamFormatDataService.updateBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
+      expect(notificationService.error).toHaveBeenCalled();
+      expect(router.navigate).not.toHaveBeenCalled();
+
+    });
+  });
+});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0fdcc75689b3552929b0f9bdef414b404921eeb5
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts
@@ -0,0 +1,62 @@
+import { map, take } from 'rxjs/operators';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Observable } from 'rxjs';
+import { Component, OnInit } from '@angular/core';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
+import { TranslateService } from '@ngx-translate/core';
+
+/**
+ * This component renders the edit page of a bitstream format.
+ * The route parameter 'id' is used to request the bitstream format.
+ */
+@Component({
+  selector: 'ds-edit-bitstream-format',
+  templateUrl: './edit-bitstream-format.component.html',
+})
+export class EditBitstreamFormatComponent implements OnInit {
+
+  /**
+   * The bitstream format wrapped in a remote-data object
+   */
+  bitstreamFormatRD$: Observable<RemoteData<BitstreamFormat>>;
+
+  constructor(
+    private route: ActivatedRoute,
+    private router: Router,
+    private notificationService: NotificationsService,
+    private translateService: TranslateService,
+    private bitstreamFormatDataService: BitstreamFormatDataService,
+  ) {
+  }
+
+  ngOnInit(): void {
+    this.bitstreamFormatRD$ = this.route.data.pipe(
+      map((data) => data.bitstreamFormat as RemoteData<BitstreamFormat>)
+    );
+  }
+
+  /**
+   * Updates the bitstream format based on the provided bitstream format emitted by the form.
+   * When successful, a success notification will be shown and the user will be navigated back to the overview page.
+   * When failed, an error  notification will be shown.
+   */
+  updateFormat(bitstreamFormat: BitstreamFormat) {
+    this.bitstreamFormatDataService.updateBitstreamFormat(bitstreamFormat).pipe(take(1)
+    ).subscribe((response: RestResponse) => {
+        if (response.isSuccessful) {
+          this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.edit.success.head'),
+            this.translateService.get('admin.registries.bitstream-formats.edit.success.content'));
+          this.router.navigate([getBitstreamFormatsModulePath()]);
+        } else {
+          this.notificationService.error('admin.registries.bitstream-formats.edit.failure.head',
+            'admin.registries.bitstream-formats.create.edit.content');
+        }
+      }
+    );
+  }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..be6ebf2599333fe332be03de4435a1aefa7b9d11
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html
@@ -0,0 +1,3 @@
+<ds-form *ngIf="formModel"
+         [formId]="'comcol-form-id'"
+         [formModel]="formModel" (submitForm)="onSubmit()" (cancel)="onCancel()"></ds-form>
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2870705fc8fa2b6d27e6307a434e5abca6ebd330
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts
@@ -0,0 +1,104 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+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 { RouterStub } from '../../../../shared/testing/router-stub';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
+import { FormatFormComponent } from './format-form.component';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
+import { DynamicCheckboxModel, DynamicFormArrayModel, DynamicInputModel } from '@ng-dynamic-forms/core';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { isEmpty } from '../../../../shared/empty.util';
+
+describe('FormatFormComponent', () => {
+  let comp: FormatFormComponent;
+  let fixture: ComponentFixture<FormatFormComponent>;
+
+  const router = new RouterStub();
+
+  const bitstreamFormat = new BitstreamFormat();
+  bitstreamFormat.uuid = 'test-uuid-1';
+  bitstreamFormat.id = 'test-uuid-1';
+  bitstreamFormat.shortDescription = 'Unknown';
+  bitstreamFormat.description = 'Unknown data format';
+  bitstreamFormat.mimetype = 'application/octet-stream';
+  bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
+  bitstreamFormat.internal = false;
+  bitstreamFormat.extensions = [];
+
+  const submittedBitstreamFormat = new BitstreamFormat();
+  submittedBitstreamFormat.id = bitstreamFormat.id;
+  submittedBitstreamFormat.shortDescription = bitstreamFormat.shortDescription;
+  submittedBitstreamFormat.mimetype = bitstreamFormat.mimetype;
+  submittedBitstreamFormat.description = bitstreamFormat.description;
+  submittedBitstreamFormat.supportLevel = bitstreamFormat.supportLevel;
+  submittedBitstreamFormat.internal = bitstreamFormat.internal;
+  submittedBitstreamFormat.extensions = bitstreamFormat.extensions;
+
+  const initAsync = () => {
+    TestBed.configureTestingModule({
+      imports: [CommonModule, RouterTestingModule.withRoutes([]), ReactiveFormsModule, FormsModule, TranslateModule.forRoot(), NgbModule.forRoot()],
+      declarations: [FormatFormComponent],
+      providers: [
+        {provide: Router, useValue: router},
+      ],
+      schemas: [CUSTOM_ELEMENTS_SCHEMA]
+    }).compileComponents();
+  };
+
+  const initBeforeEach = () => {
+    fixture = TestBed.createComponent(FormatFormComponent);
+    comp = fixture.componentInstance;
+
+    comp.bitstreamFormat = bitstreamFormat;
+    fixture.detectChanges();
+  };
+
+  describe('initialise', () => {
+    beforeEach(async(initAsync));
+    beforeEach(initBeforeEach);
+    it('should initialises the values in the form', () => {
+
+      expect((comp.formModel[0] as DynamicInputModel).value).toBe(bitstreamFormat.shortDescription);
+      expect((comp.formModel[1] as DynamicInputModel).value).toBe(bitstreamFormat.mimetype);
+      expect((comp.formModel[2] as DynamicInputModel).value).toBe(bitstreamFormat.description);
+      expect((comp.formModel[3] as DynamicInputModel).value).toBe(bitstreamFormat.supportLevel);
+      expect((comp.formModel[4] as DynamicCheckboxModel).value).toBe(bitstreamFormat.internal);
+
+      const formArray = (comp.formModel[5] as DynamicFormArrayModel);
+      const extensions = [];
+      for (let i = 0; i < formArray.groups.length; i++) {
+        const value = (formArray.get(i).get(0) as DynamicInputModel).value;
+        if (!isEmpty(value)) {
+          extensions.push((formArray.get(i).get(0) as DynamicInputModel).value);
+        }
+      }
+
+      expect(extensions).toEqual(bitstreamFormat.extensions);
+
+    });
+  });
+  describe('onSubmit', () => {
+    beforeEach(async(initAsync));
+    beforeEach(initBeforeEach);
+
+    it('should emit the bitstreamFormat currently present in the form', () => {
+      spyOn(comp.updatedFormat, 'emit');
+      comp.onSubmit();
+
+      expect(comp.updatedFormat.emit).toHaveBeenCalledWith(submittedBitstreamFormat);
+    });
+  });
+  describe('onCancel', () => {
+    beforeEach(async(initAsync));
+    beforeEach(initBeforeEach);
+
+    it('should navigate back to the bitstream overview', () => {
+      comp.onCancel();
+      expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
+    });
+  });
+});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..505ccccd91a9489bdc25216da7efb24b862d6c41
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts
@@ -0,0 +1,194 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
+import {
+  DynamicCheckboxModel,
+  DynamicFormArrayModel,
+  DynamicFormControlLayout, DynamicFormControlLayoutConfig,
+  DynamicFormControlModel,
+  DynamicFormService,
+  DynamicInputModel,
+  DynamicSelectModel,
+  DynamicTextAreaModel
+} from '@ng-dynamic-forms/core';
+import { Router } from '@angular/router';
+import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
+import { hasValue, isEmpty } from '../../../../shared/empty.util';
+import { TranslateService } from '@ngx-translate/core';
+
+/**
+ * The component responsible for rendering the form to create/edit a bitstream format
+ */
+@Component({
+  selector: 'ds-bitstream-format-form',
+  templateUrl: './format-form.component.html'
+})
+export class FormatFormComponent implements OnInit {
+
+  /**
+   * The current bitstream format
+   * This can either be and existing one or a new one
+   */
+  @Input() bitstreamFormat: BitstreamFormat = new BitstreamFormat();
+
+  /**
+   * EventEmitter that will emit the updated bitstream format
+   */
+  @Output() updatedFormat: EventEmitter<BitstreamFormat> = new EventEmitter<BitstreamFormat>();
+
+  /**
+   * The different supported support level of the bitstream format
+   */
+  supportLevelOptions = [{label: BitstreamFormatSupportLevel.Known, value: BitstreamFormatSupportLevel.Known},
+    {label: BitstreamFormatSupportLevel.Unknown, value: BitstreamFormatSupportLevel.Unknown},
+    {label: BitstreamFormatSupportLevel.Supported, value: BitstreamFormatSupportLevel.Supported}];
+
+  /**
+   * Styling element for repeatable field
+   */
+  arrayElementLayout: DynamicFormControlLayout = {
+    grid: {
+      group: 'form-row',
+    },
+  };
+
+  /**
+   * Styling element for element of repeatable field
+   */
+  arrayInputElementLayout: DynamicFormControlLayout = {
+    grid: {
+      host: 'col'
+    }
+  };
+
+  /**
+   * The form model representing the bitstream format
+   */
+  formModel: DynamicFormControlModel[] = [
+    new DynamicInputModel({
+      id: 'shortDescription',
+      name: 'shortDescription',
+      label: 'admin.registries.bitstream-formats.edit.shortDescription.label',
+      hint: 'admin.registries.bitstream-formats.edit.shortDescription.hint',
+      required: true,
+      validators: {
+        required: null
+      },
+      errorMessages: {
+        required: 'Please enter a name for this bitstream format'
+      },
+    }),
+    new DynamicInputModel({
+      id: 'mimetype',
+      name: 'mimetype',
+      label: 'admin.registries.bitstream-formats.edit.mimetype.label',
+      hint: 'admin.registries.bitstream-formats.edit.mimetype.hint',
+
+    }),
+    new DynamicTextAreaModel({
+      id: 'description',
+      name: 'description',
+      label: 'admin.registries.bitstream-formats.edit.description.label',
+      hint: 'admin.registries.bitstream-formats.edit.description.hint',
+
+    }),
+    new DynamicSelectModel({
+      id: 'supportLevel',
+      name: 'supportLevel',
+      options: this.supportLevelOptions,
+      label: 'admin.registries.bitstream-formats.edit.supportLevel.label',
+      hint: 'admin.registries.bitstream-formats.edit.supportLevel.hint',
+      value: this.supportLevelOptions[0].value
+
+    }),
+    new DynamicCheckboxModel({
+      id: 'internal',
+      name: 'internal',
+      label: 'Internal',
+      hint: 'admin.registries.bitstream-formats.edit.internal.hint',
+    }),
+    new DynamicFormArrayModel({
+      id: 'extensions',
+      name: 'extensions',
+      label: 'admin.registries.bitstream-formats.edit.extensions.label',
+      groupFactory: () => [
+        new DynamicInputModel({
+          id: 'extension',
+          placeholder: 'admin.registries.bitstream-formats.edit.extensions.placeholder',
+        }, this.arrayInputElementLayout)
+      ]
+    }, this.arrayElementLayout),
+  ];
+
+  constructor(private dynamicFormService: DynamicFormService,
+              private translateService: TranslateService,
+              private router: Router) {
+
+  }
+
+  ngOnInit(): void {
+
+    this.initValues();
+  }
+
+  /**
+   * Initializes the form based on the provided bitstream format
+   */
+  initValues() {
+    this.formModel.forEach(
+      (fieldModel: DynamicFormControlModel) => {
+        if (fieldModel.name === 'extensions') {
+          if (hasValue(this.bitstreamFormat.extensions)) {
+            const extenstions = this.bitstreamFormat.extensions;
+            const formArray = (fieldModel as DynamicFormArrayModel);
+            for (let i = 0; i < extenstions.length; i++) {
+              formArray.insertGroup(i).group[0] = new DynamicInputModel({
+                id: `extension-${i}`,
+                value: extenstions[i]
+              }, this.arrayInputElementLayout);
+            }
+          }
+        } else {
+          if (hasValue(this.bitstreamFormat[fieldModel.name])) {
+            (fieldModel as DynamicInputModel).value = this.bitstreamFormat[fieldModel.name];
+          }
+        }
+      });
+  }
+
+  /**
+   * Creates an updated bistream format based on the current values in the form
+   * Emits the updated bitstream format trouhg the updatedFormat emitter
+   */
+  onSubmit() {
+    const updatedBitstreamFormat = Object.assign(new BitstreamFormat(),
+      {
+        id: this.bitstreamFormat.id
+      });
+
+    this.formModel.forEach(
+      (fieldModel: DynamicFormControlModel) => {
+        if (fieldModel.name === 'extensions') {
+          const formArray = (fieldModel as DynamicFormArrayModel);
+          const extensions = [];
+          for (let i = 0; i < formArray.groups.length; i++) {
+            const value = (formArray.get(i).get(0) as DynamicInputModel).value;
+            if (!isEmpty(value)) {
+              extensions.push((formArray.get(i).get(0) as DynamicInputModel).value);
+            }
+          }
+          updatedBitstreamFormat.extensions = extensions;
+        } else {
+          updatedBitstreamFormat[fieldModel.name] = (fieldModel as DynamicInputModel).value;
+        }
+      });
+    this.updatedFormat.emit(updatedBitstreamFormat);
+  }
+
+  /**
+   * Cancels the edit/create action of the bitstream format and navigates back to the bitstream format registry
+   */
+  onCancel() {
+    this.router.navigate([getBitstreamFormatsModulePath()]);
+  }
+}
diff --git a/src/app/+admin/admin-routing.module.ts b/src/app/+admin/admin-routing.module.ts
index 71af51c68347cb9198a92ed0b8ee0cef409cc6ab..2003ecf124a004de9698ed055c824c5a8cdc1242 100644
--- a/src/app/+admin/admin-routing.module.ts
+++ b/src/app/+admin/admin-routing.module.ts
@@ -1,11 +1,19 @@
 import { RouterModule } from '@angular/router';
 import { NgModule } from '@angular/core';
+import { URLCombiner } from '../core/url-combiner/url-combiner';
+import { getAdminModulePath } from '../app-routing.module';
+
+const REGISTRIES_MODULE_PATH = 'registries';
+
+export function getRegistriesModulePath() {
+  return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString();
+}
 
 @NgModule({
   imports: [
     RouterModule.forChild([
       {
-        path: 'registries',
+        path: REGISTRIES_MODULE_PATH,
         loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule'
       }
     ])
diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html
index 91239de17c64de4fa8a0b4351b0503c557645f64..2b16bc1ca688a9c797d0a38340432c8a72bc001d 100644
--- a/src/app/+collection-page/collection-page.component.html
+++ b/src/app/+collection-page/collection-page.component.html
@@ -52,6 +52,9 @@
                           message="{{'error.recent-submissions' | translate}}"></ds-error>
                 <ds-loading *ngIf="!itemRD || itemRD.isLoading"
                             message="{{'loading.recent-submissions' | translate}}"></ds-loading>
+                <div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
+                        {{'collection.page.browse.recent.empty' | translate}}
+                </div>
             </ng-container>
         </div>
         <ds-error *ngIf="collectionRD?.hasFailed"
diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts
index 6743028b6c37760bb621bcfb0e41026356c284de..f510ccf19be23185e4282993bf92436bd40306bf 100644
--- a/src/app/+item-page/item-page.module.ts
+++ b/src/app/+item-page/item-page.module.ts
@@ -62,7 +62,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field
     GenericItemPageFieldComponent,
     RelatedEntitiesSearchComponent,
     RelatedItemsComponent,
-    MetadataRepresentationListComponent
+    MetadataRepresentationListComponent,
+    ItemPageTitleFieldComponent
   ],
   entryComponents: [
     PublicationComponent
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 86364aca895d831432b60da2d21137da9e20180a..e1ddc2b8895752bbe1002933ae58072677b068f3 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -16,6 +16,12 @@ const COMMUNITY_MODULE_PATH = 'communities';
 export function getCommunityModulePath() {
   return `/${COMMUNITY_MODULE_PATH}`;
 }
+
+const  ADMIN_MODULE_PATH = 'admin';
+export function getAdminModulePath() {
+  return `/${ADMIN_MODULE_PATH}`;
+}
+
 @NgModule({
   imports: [
     RouterModule.forRoot([
@@ -27,7 +33,7 @@ export function getCommunityModulePath() {
       { path: 'mydspace', loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule', canActivate: [AuthenticatedGuard] },
       { path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
       { path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
-      { path: 'admin', loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] },
+      { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] },
       { path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
       { path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
       { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },
diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts
index ea2512a974464c042ff49a36f28cfbfaf4217936..e3333fb34a279d0059d520c2cab4d3d08a2dbe06 100644
--- a/src/app/app.reducer.ts
+++ b/src/app/app.reducer.ts
@@ -23,6 +23,10 @@ import { hasValue } from './shared/empty.util';
 import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
 import { menusReducer, MenusState } from './shared/menu/menu.reducer';
 import { historyReducer, HistoryState } from './shared/history/history.reducer';
+import {
+  bitstreamFormatReducer,
+  BitstreamFormatRegistryState
+} from './+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
 
 export interface AppState {
   router: fromRouter.RouterReducerState;
@@ -30,6 +34,7 @@ export interface AppState {
   hostWindow: HostWindowState;
   forms: FormState;
   metadataRegistry: MetadataRegistryState;
+  bitstreamFormats: BitstreamFormatRegistryState;
   notifications: NotificationsState;
   searchSidebar: SearchSidebarState;
   searchFilter: SearchFiltersState;
@@ -44,6 +49,7 @@ export const appReducers: ActionReducerMap<AppState> = {
   hostWindow: hostWindowReducer,
   forms: formReducer,
   metadataRegistry: metadataRegistryReducer,
+  bitstreamFormats: bitstreamFormatReducer,
   notifications: notificationsReducer,
   searchSidebar: sidebarReducer,
   searchFilter: filterReducer,
diff --git a/src/app/core/cache/models/normalized-bitstream-format.model.ts b/src/app/core/cache/models/normalized-bitstream-format.model.ts
index 5ee135b530a8e629288b15243f58cae7c9a6219a..2283ecb368c47d63b8f0f3ba7f42a5b7b7ee1d42 100644
--- a/src/app/core/cache/models/normalized-bitstream-format.model.ts
+++ b/src/app/core/cache/models/normalized-bitstream-format.model.ts
@@ -4,7 +4,7 @@ import { BitstreamFormat } from '../../shared/bitstream-format.model';
 import { mapsTo } from '../builders/build-decorators';
 import { IDToUUIDSerializer } from '../id-to-uuid-serializer';
 import { NormalizedObject } from './normalized-object.model';
-import { SupportLevel } from './support-level.model';
+import { BitstreamFormatSupportLevel } from '../../shared/bitstream-format-support-level';
 
 /**
  * Normalized model class for a Bitstream Format
@@ -34,7 +34,7 @@ export class NormalizedBitstreamFormat extends NormalizedObject<BitstreamFormat>
    * The level of support the system offers for this Bitstream Format
    */
   @autoserialize
-  supportLevel: SupportLevel;
+  supportLevel: BitstreamFormatSupportLevel;
 
   /**
    * True if the Bitstream Format is used to store system information, rather than the content of items in the system
@@ -46,7 +46,7 @@ export class NormalizedBitstreamFormat extends NormalizedObject<BitstreamFormat>
    * String representing this Bitstream Format's file extension
    */
   @autoserialize
-  extensions: string;
+  extensions: string[];
 
   /**
    * Identifier for this Bitstream Format
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 1d22b5fefe23467c0225a7d8cc37b62ec185ae17..60d1bce3b8be7621f2c1b2f95084b910bf77a066 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -107,6 +107,7 @@ import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing
 import { ClaimedTaskDataService } from './tasks/claimed-task-data.service';
 import { PoolTaskDataService } from './tasks/pool-task-data.service';
 import { TaskResponseParsingService } from './tasks/task-response-parsing.service';
+import { BitstreamFormatDataService } from './data/bitstream-format-data.service';
 import { NormalizedClaimedTask } from './tasks/models/normalized-claimed-task-object.model';
 import { NormalizedTaskObject } from './tasks/models/normalized-task-object.model';
 import { NormalizedPoolTask } from './tasks/models/normalized-pool-task-object.model';
@@ -153,6 +154,7 @@ const PROVIDERS = [
   PaginationComponentOptions,
   ResourcePolicyService,
   RegistryService,
+  BitstreamFormatDataService,
   NormalizedObjectBuildService,
   RemoteDataBuildService,
   RequestService,
diff --git a/src/app/core/data/bitstream-format-data.service.spec.ts b/src/app/core/data/bitstream-format-data.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f3ce4782366df8c6fff3d7fd5f6e3529f2f2d151
--- /dev/null
+++ b/src/app/core/data/bitstream-format-data.service.spec.ts
@@ -0,0 +1,293 @@
+import { BitstreamFormatDataService } from './bitstream-format-data.service';
+import { RequestEntry } from './request.reducer';
+import { RestResponse } from '../cache/response.models';
+import { Observable, of as observableOf } from 'rxjs';
+import { Action, Store } from '@ngrx/store';
+import { CoreState } from '../core.reducers';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { cold, getTestScheduler, hot } from 'jasmine-marbles';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { HttpClient } from '@angular/common/http';
+import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
+import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
+import { BitstreamFormat } from '../shared/bitstream-format.model';
+import { async } from '@angular/core/testing';
+import {
+  BitstreamFormatsRegistryDeselectAction,
+  BitstreamFormatsRegistryDeselectAllAction,
+  BitstreamFormatsRegistrySelectAction
+} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
+import { TestScheduler } from 'rxjs/testing';
+
+describe('BitstreamFormatDataService', () => {
+  let service: BitstreamFormatDataService;
+  let requestService;
+  let scheduler: TestScheduler;
+
+  const bitstreamFormatsEndpoint = 'https://rest.api/core/bitstream-formats';
+  const bitstreamFormatsIdEndpoint = 'https://rest.api/core/bitstream-formats/format-id';
+
+  const responseCacheEntry = new RequestEntry();
+  responseCacheEntry.response = new RestResponse(true, 200, 'Success');
+  responseCacheEntry.completed = true;
+
+  const store = {
+    dispatch(action: Action) {
+      // Do Nothing
+    }
+  } as Store<CoreState>;
+
+  const objectCache = {} as ObjectCacheService;
+  const halEndpointService = {
+    getEndpoint(linkPath: string): Observable<string> {
+      return cold('a', {a: bitstreamFormatsEndpoint});
+    }
+  } as HALEndpointService;
+
+  const notificationsService = {} as NotificationsService;
+  const http = {} as HttpClient;
+  const comparator = {} as any;
+  const dataBuildService = {} as NormalizedObjectBuildService;
+  const rdbService = {} as RemoteDataBuildService;
+
+  function initTestService(halService) {
+    return new BitstreamFormatDataService(
+      requestService,
+      rdbService,
+      dataBuildService,
+      store,
+      objectCache,
+      halService,
+      notificationsService,
+      http,
+      comparator
+    );
+  }
+
+  describe('getBrowseEndpoint', () => {
+    beforeEach(async(() => {
+      scheduler = getTestScheduler();
+      requestService = jasmine.createSpyObj('requestService', {
+        configure: {},
+        getByHref: observableOf(responseCacheEntry),
+        getByUUID: cold('a', {a: responseCacheEntry}),
+        generateRequestId: 'request-id',
+        removeByHrefSubstring: {}
+      });
+      service = initTestService(halEndpointService);
+    }));
+    it('should get the browse endpoint', () => {
+      const result = service.getBrowseEndpoint();
+      const expected = cold('b', {b: bitstreamFormatsEndpoint});
+
+      expect(result).toBeObservable(expected);
+    });
+  });
+
+  describe('getUpdateEndpoint', () => {
+    beforeEach(async(() => {
+      scheduler = getTestScheduler();
+      requestService = jasmine.createSpyObj('requestService', {
+        configure: {},
+        getByHref: observableOf(responseCacheEntry),
+        getByUUID: cold('a', {a: responseCacheEntry}),
+        generateRequestId: 'request-id',
+        removeByHrefSubstring: {}
+      });
+      service = initTestService(halEndpointService);
+    }));
+    it('should get the update endpoint', () => {
+      const formatId = 'format-id';
+
+      const result = service.getUpdateEndpoint(formatId);
+      const expected = cold('b', {b: bitstreamFormatsIdEndpoint});
+
+      expect(result).toBeObservable(expected);
+    });
+  });
+
+  describe('getCreateEndpoint', () => {
+    beforeEach(async(() => {
+      scheduler = getTestScheduler();
+      requestService = jasmine.createSpyObj('requestService', {
+        configure: {},
+        getByHref: observableOf(responseCacheEntry),
+        getByUUID: cold('a', {a: responseCacheEntry}),
+        generateRequestId: 'request-id',
+        removeByHrefSubstring: {}
+      });
+      service = initTestService(halEndpointService);
+    }));
+    it('should get the create endpoint ', () => {
+
+      const result = service.getCreateEndpoint();
+      const expected = cold('b', {b: bitstreamFormatsEndpoint});
+
+      expect(result).toBeObservable(expected);
+    });
+  });
+
+  describe('updateBitstreamFormat', () => {
+    beforeEach(async(() => {
+      scheduler = getTestScheduler();
+      requestService = jasmine.createSpyObj('requestService', {
+        configure: {},
+        getByHref: observableOf(responseCacheEntry),
+        getByUUID: cold('a', {a: responseCacheEntry}),
+        generateRequestId: 'request-id',
+        removeByHrefSubstring: {}
+      });
+      service = initTestService(halEndpointService);
+    }));
+    it('should update the bitstream format', () => {
+      const updatedBistreamFormat = new BitstreamFormat();
+      updatedBistreamFormat.uuid = 'updated-uuid';
+
+      const expected = cold('(b)', {b: new RestResponse(true, 200, 'Success')});
+      const result = service.updateBitstreamFormat(updatedBistreamFormat);
+
+      expect(result).toBeObservable(expected);
+
+    });
+  });
+
+  describe('createBitstreamFormat', () => {
+    beforeEach(async(() => {
+      scheduler = getTestScheduler();
+      requestService = jasmine.createSpyObj('requestService', {
+        configure: {},
+        getByHref: observableOf(responseCacheEntry),
+        getByUUID: cold('a', {a: responseCacheEntry}),
+        generateRequestId: 'request-id',
+        removeByHrefSubstring: {}
+      });
+      service = initTestService(halEndpointService);
+    }));
+    it('should create a new bitstream format', () => {
+      const newFormat = new BitstreamFormat();
+      newFormat.uuid = 'new-uuid';
+
+      const expected = cold('(b)', {b: new RestResponse(true, 200, 'Success')});
+      const result = service.createBitstreamFormat(newFormat);
+
+      expect(result).toBeObservable(expected);
+    });
+  });
+
+  describe('clearBitStreamFormatRequests', () => {
+    beforeEach(async(() => {
+      scheduler = getTestScheduler();
+      requestService = jasmine.createSpyObj('requestService', {
+        configure: {},
+        getByHref: observableOf(responseCacheEntry),
+        getByUUID: cold('a', {a: responseCacheEntry}),
+        generateRequestId: 'request-id',
+        removeByHrefSubstring: {}
+      });
+      const halService = {
+        getEndpoint(linkPath: string): Observable<string> {
+          return observableOf(bitstreamFormatsEndpoint);
+        }
+      } as HALEndpointService;
+      service = initTestService(halService);
+      service.clearBitStreamFormatRequests().subscribe();
+    }));
+    it('should remove the bitstream format hrefs in the request service', () => {
+      expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(bitstreamFormatsEndpoint);
+    });
+  });
+
+  describe('selectBitstreamFormat', () => {
+    beforeEach(async(() => {
+      scheduler = getTestScheduler();
+      requestService = jasmine.createSpyObj('requestService', {
+        configure: {},
+        getByHref: observableOf(responseCacheEntry),
+        getByUUID: cold('a', {a: responseCacheEntry}),
+        generateRequestId: 'request-id',
+        removeByHrefSubstring: {}
+      });
+      service = initTestService(halEndpointService);
+      spyOn(store, 'dispatch');
+    }));
+    it('should add a selected bitstream to the store', () => {
+      const format = new BitstreamFormat();
+      format.uuid = 'uuid';
+
+      service.selectBitstreamFormat(format);
+      expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistrySelectAction(format));
+    });
+  });
+
+  describe('deselectBitstreamFormat', () => {
+    beforeEach(async(() => {
+      scheduler = getTestScheduler();
+      requestService = jasmine.createSpyObj('requestService', {
+        configure: {},
+        getByHref: observableOf(responseCacheEntry),
+        getByUUID: cold('a', {a: responseCacheEntry}),
+        generateRequestId: 'request-id',
+        removeByHrefSubstring: {}
+      });
+      service = initTestService(halEndpointService);
+      spyOn(store, 'dispatch');
+    }));
+    it('should remove a bitstream from the store', () => {
+      const format = new BitstreamFormat();
+      format.uuid = 'uuid';
+
+      service.deselectBitstreamFormat(format);
+      expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAction(format));
+    });
+  });
+
+  describe('deselectAllBitstreamFormats', () => {
+    beforeEach(async(() => {
+      scheduler = getTestScheduler();
+      requestService = jasmine.createSpyObj('requestService', {
+        configure: {},
+        getByHref: observableOf(responseCacheEntry),
+        getByUUID: cold('a', {a: responseCacheEntry}),
+        generateRequestId: 'request-id',
+        removeByHrefSubstring: {}
+      });
+      service = initTestService(halEndpointService);
+      spyOn(store, 'dispatch');
+
+    }));
+    it('should remove all bitstreamFormats from the store', () => {
+      service.deselectAllBitstreamFormats();
+      expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAllAction());
+    });
+  });
+
+  describe('delete', () => {
+    beforeEach(async(() => {
+      scheduler = getTestScheduler();
+      requestService = jasmine.createSpyObj('requestService', {
+        configure: {},
+        getByHref: observableOf(responseCacheEntry),
+        getByUUID: hot('a', {a: responseCacheEntry}),
+        generateRequestId: 'request-id',
+        removeByHrefSubstring: {}
+      });
+      const halService = {
+        getEndpoint(linkPath: string): Observable<string> {
+          return observableOf(bitstreamFormatsEndpoint);
+        }
+      } as HALEndpointService;
+      service = initTestService(halService);
+    }));
+    it('should delete a bitstream format', () => {
+      const format = new BitstreamFormat();
+      format.uuid = 'format-uuid';
+      format.id = 'format-id';
+
+      const expected = cold('(b|)', {b: true});
+      const result = service.delete(format);
+
+      expect(result).toBeObservable(expected);
+    });
+  });
+});
diff --git a/src/app/core/data/bitstream-format-data.service.ts b/src/app/core/data/bitstream-format-data.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a5638183c01031b463a17d9a1a35d0615a5ec8da
--- /dev/null
+++ b/src/app/core/data/bitstream-format-data.service.ts
@@ -0,0 +1,183 @@
+import { Injectable } from '@angular/core';
+import { DataService } from './data.service';
+import { BitstreamFormat } from '../shared/bitstream-format.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 { createSelector, select, Store } from '@ngrx/store';
+import { CoreState } from '../core.reducers';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { HttpClient } from '@angular/common/http';
+import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
+import { DeleteByIDRequest, FindAllOptions, PostRequest, PutRequest } from './request.models';
+import { Observable } from 'rxjs';
+import { find, map, tap } from 'rxjs/operators';
+import { configureRequest, getResponseFromEntry } from '../shared/operators';
+import { distinctUntilChanged } from 'rxjs/internal/operators/distinctUntilChanged';
+import { RestResponse } from '../cache/response.models';
+import { AppState } from '../../app.reducer';
+import { BitstreamFormatRegistryState } from '../../+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
+import {
+  BitstreamFormatsRegistryDeselectAction,
+  BitstreamFormatsRegistryDeselectAllAction,
+  BitstreamFormatsRegistrySelectAction
+} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
+import { hasValue } from '../../shared/empty.util';
+import { RequestEntry } from './request.reducer';
+
+const bitstreamFormatsStateSelector = (state: AppState) => state.bitstreamFormats;
+const selectedBitstreamFormatSelector = createSelector(bitstreamFormatsStateSelector,
+  (bitstreamFormatRegistryState: BitstreamFormatRegistryState) => bitstreamFormatRegistryState.selectedBitstreamFormats);
+
+/**
+ * A service responsible for fetching/sending data from/to the REST API on the bitstreamformats endpoint
+ */
+@Injectable()
+export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
+
+  protected linkPath = 'bitstreamformats';
+  protected forceBypassCache = false;
+
+  constructor(
+    protected requestService: RequestService,
+    protected rdbService: RemoteDataBuildService,
+    protected dataBuildService: NormalizedObjectBuildService,
+    protected store: Store<CoreState>,
+    protected objectCache: ObjectCacheService,
+    protected halService: HALEndpointService,
+    protected notificationsService: NotificationsService,
+    protected http: HttpClient,
+    protected comparator: DefaultChangeAnalyzer<BitstreamFormat>) {
+    super();
+  }
+
+  /**
+   * Get the endpoint for browsing bitstream formats
+   * @param {FindAllOptions} options
+   * @returns {Observable<string>}
+   */
+  getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable<string> {
+    return this.halService.getEndpoint(this.linkPath);
+  }
+
+  /**
+   * Get the endpoint to update an existing bitstream format
+   * @param formatId
+   */
+  public getUpdateEndpoint(formatId: string): Observable<string> {
+    return this.getBrowseEndpoint().pipe(
+      map((endpoint: string) => this.getIDHref(endpoint, formatId))
+    );
+  }
+
+  /**
+   * Get the endpoint to create a new bitstream format
+   */
+  public getCreateEndpoint(): Observable<string> {
+    return this.getBrowseEndpoint();
+  }
+
+  /**
+   * Update an existing bitstreamFormat
+   * @param bitstreamFormat
+   */
+  updateBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable<RestResponse> {
+    const requestId = this.requestService.generateRequestId();
+
+    this.getUpdateEndpoint(bitstreamFormat.id).pipe(
+      distinctUntilChanged(),
+      map((endpointURL: string) =>
+        new PutRequest(requestId, endpointURL, bitstreamFormat)),
+      configureRequest(this.requestService)).subscribe();
+
+    return this.requestService.getByUUID(requestId).pipe(
+      getResponseFromEntry()
+    );
+
+  }
+
+  /**
+   * Create a new BitstreamFormat
+   * @param BitstreamFormat
+   */
+  public createBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable<RestResponse> {
+    const requestId = this.requestService.generateRequestId();
+
+    this.getCreateEndpoint().pipe(
+      map((endpointURL: string) => {
+        return new PostRequest(requestId, endpointURL, bitstreamFormat);
+      }),
+      configureRequest(this.requestService)
+    ).subscribe();
+
+    return this.requestService.getByUUID(requestId).pipe(
+      getResponseFromEntry()
+    );
+  }
+
+  /**
+   * Clears the cache of the list of BitstreamFormats
+   */
+  public clearBitStreamFormatRequests(): Observable<string> {
+    return this.getBrowseEndpoint().pipe(
+      tap((href: string) => this.requestService.removeByHrefSubstring(href))
+    );
+  }
+
+  /**
+   * Gets all the selected BitstreamFormats from the store
+   */
+  public getSelectedBitstreamFormats(): Observable<BitstreamFormat[]> {
+    return this.store.pipe(select(selectedBitstreamFormatSelector));
+  }
+
+  /**
+   * Adds a BistreamFormat to the selected BitstreamFormats in the store
+   * @param bitstreamFormat
+   */
+  public selectBitstreamFormat(bitstreamFormat: BitstreamFormat) {
+    this.store.dispatch(new BitstreamFormatsRegistrySelectAction(bitstreamFormat));
+  }
+
+  /**
+   * Removes a BistreamFormat from the list of selected BitstreamFormats in the store
+   * @param bitstreamFormat
+   */
+  public deselectBitstreamFormat(bitstreamFormat: BitstreamFormat) {
+    this.store.dispatch(new BitstreamFormatsRegistryDeselectAction(bitstreamFormat));
+  }
+
+  /**
+   * Removes all BitstreamFormats from the list of selected BitstreamFormats in the store
+   */
+  public deselectAllBitstreamFormats() {
+    this.store.dispatch(new BitstreamFormatsRegistryDeselectAllAction());
+  }
+
+  /**
+   * Delete an existing DSpace Object on the server
+   * @param format The DSpace Object to be removed
+   * Return an observable that emits true when the deletion was successful, false when it failed
+   */
+  delete(format: BitstreamFormat): Observable<boolean> {
+    const requestId = this.requestService.generateRequestId();
+
+    const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
+      map((endpoint: string) => this.getIDHref(endpoint, format.id)));
+
+    hrefObs.pipe(
+      find((href: string) => hasValue(href)),
+      map((href: string) => {
+        const request = new DeleteByIDRequest(requestId, href, format.id);
+        this.requestService.configure(request);
+      })
+    ).subscribe();
+
+    return this.requestService.getByUUID(requestId).pipe(
+      find((request: RequestEntry) => request.completed),
+      map((request: RequestEntry) => request.response.isSuccessful)
+    );
+  }
+}
diff --git a/src/app/core/registry/mock-bitstream-format.model.ts b/src/app/core/registry/mock-bitstream-format.model.ts
deleted file mode 100644
index f5811e367c77494b9800504a9359a8cd0312d663..0000000000000000000000000000000000000000
--- a/src/app/core/registry/mock-bitstream-format.model.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export class BitstreamFormat {
-  shortDescription: string;
-  description: string;
-  mimetype: string;
-  supportLevel: number;
-  internal: boolean;
-  extensions: string;
-}
diff --git a/src/app/core/registry/registry.service.spec.ts b/src/app/core/registry/registry.service.spec.ts
index 47e306d62483e06988c518b775f440b4d24c9e31..455a8043dab4bb2ed4932e0d41a1b0e74c8f5e70 100644
--- a/src/app/core/registry/registry.service.spec.ts
+++ b/src/app/core/registry/registry.service.spec.ts
@@ -12,7 +12,6 @@ import { PageInfo } from '../shared/page-info.model';
 import { getMockRequestService } from '../../shared/mocks/mock-request.service';
 
 import {
-  RegistryBitstreamformatsSuccessResponse,
   RegistryMetadatafieldsSuccessResponse,
   RegistryMetadataschemasSuccessResponse,
   RestResponse
@@ -20,7 +19,6 @@ import {
 import { Component } from '@angular/core';
 import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model';
 import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model';
-import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model';
 import { map } from 'rxjs/operators';
 import { Store, StoreModule } from '@ngrx/store';
 import { MockStore } from '../../shared/testing/mock-store';
@@ -44,7 +42,7 @@ import { MetadataSchema } from '../metadata/metadata-schema.model';
 import { MetadataField } from '../metadata/metadata-field.model';
 import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
 
-@Component({ template: '' })
+@Component({template: ''})
 class DummyComponent {
 }
 
@@ -127,7 +125,7 @@ describe('RegistryService', () => {
     toRemoteDataObservable: (requestEntryObs: Observable<RequestEntry>, payloadObs: Observable<any>) => {
       return observableCombineLatest(requestEntryObs,
         payloadObs).pipe(map(([req, pay]) => {
-          return { req, pay };
+          return {req, pay};
         })
       );
     },
@@ -143,11 +141,11 @@ describe('RegistryService', () => {
         DummyComponent
       ],
       providers: [
-        { provide: RequestService, useValue: getMockRequestService() },
-        { provide: RemoteDataBuildService, useValue: rdbStub },
-        { provide: HALEndpointService, useValue: halServiceStub },
-        { provide: Store, useClass: MockStore },
-        { provide: NotificationsService, useValue: new NotificationsServiceStub() },
+        {provide: RequestService, useValue: getMockRequestService()},
+        {provide: RemoteDataBuildService, useValue: rdbStub},
+        {provide: HALEndpointService, useValue: halServiceStub},
+        {provide: Store, useClass: MockStore},
+        {provide: NotificationsService, useValue: new NotificationsServiceStub()},
         RegistryService
       ]
     });
@@ -162,7 +160,7 @@ describe('RegistryService', () => {
       page: pageInfo
     });
     const response = new RegistryMetadataschemasSuccessResponse(queryResponse, 200, 'OK', pageInfo);
-    const responseEntry = Object.assign(new RequestEntry(), { response: response });
+    const responseEntry = Object.assign(new RequestEntry(), {response: response});
 
     beforeEach(() => {
       (registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
@@ -191,7 +189,7 @@ describe('RegistryService', () => {
       page: pageInfo
     });
     const response = new RegistryMetadataschemasSuccessResponse(queryResponse, 200, 'OK', pageInfo);
-    const responseEntry = Object.assign(new RequestEntry(), { response: response });
+    const responseEntry = Object.assign(new RequestEntry(), {response: response});
 
     beforeEach(() => {
       (registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
@@ -220,7 +218,7 @@ describe('RegistryService', () => {
       page: pageInfo
     });
     const response = new RegistryMetadatafieldsSuccessResponse(queryResponse, 200, 'OK', pageInfo);
-    const responseEntry = Object.assign(new RequestEntry(), { response: response });
+    const responseEntry = Object.assign(new RequestEntry(), {response: response});
 
     beforeEach(() => {
       (registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
@@ -243,35 +241,6 @@ describe('RegistryService', () => {
     });
   });
 
-  describe('when requesting bitstreamformats', () => {
-    const queryResponse = Object.assign(new RegistryBitstreamformatsResponse(), {
-      bitstreamformats: mockFieldsList,
-      page: pageInfo
-    });
-    const response = new RegistryBitstreamformatsSuccessResponse(queryResponse, 200, 'OK', pageInfo);
-    const responseEntry = Object.assign(new RequestEntry(), { response: response });
-
-    beforeEach(() => {
-      (registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
-      /* tslint:disable:no-empty */
-      registryService.getBitstreamFormats(pagination).subscribe((value) => {
-      });
-      /* tslint:enable:no-empty */
-    });
-
-    it('should call getEndpoint on the halService', () => {
-      expect((registryService as any).halService.getEndpoint).toHaveBeenCalled();
-    });
-
-    it('should send out the request on the request service', () => {
-      expect((registryService as any).requestService.configure).toHaveBeenCalled();
-    });
-
-    it('should call getByHref on the request service with the correct request url', () => {
-      expect((registryService as any).requestService.getByHref).toHaveBeenCalledWith(endpointWithParams);
-    });
-  });
-
   describe('when dispatching to the store', () => {
     beforeEach(() => {
       spyOn(mockStore, 'dispatch');
@@ -284,7 +253,7 @@ describe('RegistryService', () => {
 
       it('should dispatch a MetadataRegistryEditSchemaAction with the correct schema', () => {
         expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditSchemaAction(mockSchemasList[0]));
-      })
+      });
     });
 
     describe('when calling cancelEditMetadataSchema', () => {
@@ -294,7 +263,7 @@ describe('RegistryService', () => {
 
       it('should dispatch a MetadataRegistryCancelSchemaAction', () => {
         expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelSchemaAction());
-      })
+      });
     });
 
     describe('when calling selectMetadataSchema', () => {
@@ -304,7 +273,7 @@ describe('RegistryService', () => {
 
       it('should dispatch a MetadataRegistrySelectSchemaAction with the correct schema', () => {
         expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectSchemaAction(mockSchemasList[0]));
-      })
+      });
     });
 
     describe('when calling deselectMetadataSchema', () => {
@@ -314,7 +283,7 @@ describe('RegistryService', () => {
 
       it('should dispatch a MetadataRegistryDeselectSchemaAction with the correct schema', () => {
         expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectSchemaAction(mockSchemasList[0]));
-      })
+      });
     });
 
     describe('when calling deselectAllMetadataSchema', () => {
@@ -324,7 +293,7 @@ describe('RegistryService', () => {
 
       it('should dispatch a MetadataRegistryDeselectAllSchemaAction', () => {
         expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllSchemaAction());
-      })
+      });
     });
 
     describe('when calling editMetadataField', () => {
@@ -334,7 +303,7 @@ describe('RegistryService', () => {
 
       it('should dispatch a MetadataRegistryEditFieldAction with the correct Field', () => {
         expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditFieldAction(mockFieldsList[0]));
-      })
+      });
     });
 
     describe('when calling cancelEditMetadataField', () => {
@@ -344,7 +313,7 @@ describe('RegistryService', () => {
 
       it('should dispatch a MetadataRegistryCancelFieldAction', () => {
         expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelFieldAction());
-      })
+      });
     });
 
     describe('when calling selectMetadataField', () => {
@@ -354,7 +323,7 @@ describe('RegistryService', () => {
 
       it('should dispatch a MetadataRegistrySelectFieldAction with the correct Field', () => {
         expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectFieldAction(mockFieldsList[0]));
-      })
+      });
     });
 
     describe('when calling deselectMetadataField', () => {
@@ -364,7 +333,7 @@ describe('RegistryService', () => {
 
       it('should dispatch a MetadataRegistryDeselectFieldAction with the correct Field', () => {
         expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectFieldAction(mockFieldsList[0]));
-      })
+      });
     });
 
     describe('when calling deselectAllMetadataField', () => {
@@ -374,7 +343,7 @@ describe('RegistryService', () => {
 
       it('should dispatch a MetadataRegistryDeselectAllFieldAction', () => {
         expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllFieldAction());
-      })
+      });
     });
   });
 
@@ -417,7 +386,7 @@ describe('RegistryService', () => {
       result.subscribe((response: RestResponse) => {
         expect(response.isSuccessful).toBe(true);
       });
-    })
+    });
   });
 
   describe('when deleteMetadataField is called', () => {
@@ -431,7 +400,7 @@ describe('RegistryService', () => {
       result.subscribe((response: RestResponse) => {
         expect(response.isSuccessful).toBe(true);
       });
-    })
+    });
   });
 
   describe('when clearMetadataSchemaRequests is called', () => {
diff --git a/src/app/core/registry/registry.service.ts b/src/app/core/registry/registry.service.ts
index d816c5eab8681f860bfe278da967e66a0eab28f7..206426588e31a9ddf24c2fd97c3e42e8eb9d8b0f 100644
--- a/src/app/core/registry/registry.service.ts
+++ b/src/app/core/registry/registry.service.ts
@@ -3,13 +3,13 @@ import { Injectable } from '@angular/core';
 import { RemoteData } from '../data/remote-data';
 import { PaginatedList } from '../data/paginated-list';
 import { PageInfo } from '../shared/page-info.model';
-import { BitstreamFormat } from './mock-bitstream-format.model';
 import {
   CreateMetadataFieldRequest,
   CreateMetadataSchemaRequest,
   DeleteRequest,
   GetRequest,
-  RestRequest, UpdateMetadataFieldRequest,
+  RestRequest,
+  UpdateMetadataFieldRequest,
   UpdateMetadataSchemaRequest
 } from '../data/request.models';
 import { GenericConstructor } from '../shared/generic-constructor';
@@ -19,24 +19,19 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
 import { RequestService } from '../data/request.service';
 import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model';
 import {
-  ErrorResponse, MetadatafieldSuccessResponse, MetadataschemaSuccessResponse,
-  RegistryBitstreamformatsSuccessResponse,
+  MetadatafieldSuccessResponse,
+  MetadataschemaSuccessResponse,
   RegistryMetadatafieldsSuccessResponse,
-  RegistryMetadataschemasSuccessResponse, RestResponse
+  RegistryMetadataschemasSuccessResponse,
+  RestResponse
 } from '../cache/response.models';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { RegistryMetadatafieldsResponseParsingService } from '../data/registry-metadatafields-response-parsing.service';
 import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model';
-import { hasValue, hasNoValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
+import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
 import { URLCombiner } from '../url-combiner/url-combiner';
 import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
-import { RegistryBitstreamformatsResponseParsingService } from '../data/registry-bitstreamformats-response-parsing.service';
-import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model';
-import {
-  configureRequest,
-  getResponseFromEntry,
-  getSucceededRemoteData
-} from '../shared/operators';
+import { configureRequest, getResponseFromEntry } from '../shared/operators';
 import { createSelector, select, Store } from '@ngrx/store';
 import { AppState } from '../../app.reducer';
 import { MetadataRegistryState } from '../../+admin/admin-registries/metadata-registry/metadata-registry.reducers';
@@ -52,9 +47,8 @@ import {
   MetadataRegistrySelectFieldAction,
   MetadataRegistrySelectSchemaAction
 } from '../../+admin/admin-registries/metadata-registry/metadata-registry.actions';
-import { distinctUntilChanged, flatMap, map, switchMap, take, tap } from 'rxjs/operators';
+import { distinctUntilChanged, flatMap, map, take, tap } from 'rxjs/operators';
 import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
-import { ResourceType } from '../shared/resource-type';
 import { NormalizedMetadataSchema } from '../metadata/normalized-metadata-schema.model';
 import { NotificationsService } from '../../shared/notifications/notifications.service';
 import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
@@ -79,7 +73,8 @@ export class RegistryService {
 
   private metadataSchemasPath = 'metadataschemas';
   private metadataFieldsPath = 'metadatafields';
-  private bitstreamFormatsPath = 'bitstreamformats';
+
+  // private bitstreamFormatsPath = 'bitstreamformats';
 
   constructor(protected requestService: RequestService,
               private rdb: RemoteDataBuildService,
@@ -197,7 +192,7 @@ export class RegistryService {
    */
   public getAllMetadataFields(pagination?: PaginationComponentOptions): Observable<RemoteData<PaginatedList<MetadataField>>> {
     if (hasNoValue(pagination)) {
-      pagination = { currentPage: 1, pageSize: 10000 } as any;
+      pagination = {currentPage: 1, pageSize: 10000} as any;
     }
     const requestObs = this.getMetadataFieldsRequestObs(pagination);
 
@@ -231,41 +226,7 @@ export class RegistryService {
     return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs);
   }
 
-  /**
-   * Retrieves all bitstream formats
-   * @param pagination The pagination info used to retrieve the bitstream formats
-   */
-  public getBitstreamFormats(pagination: PaginationComponentOptions): Observable<RemoteData<PaginatedList<BitstreamFormat>>> {
-    const requestObs = this.getBitstreamFormatsRequestObs(pagination);
-
-    const requestEntryObs = requestObs.pipe(
-      flatMap((request: RestRequest) => this.requestService.getByHref(request.href))
-    );
-
-    const rbrObs: Observable<RegistryBitstreamformatsResponse> = requestEntryObs.pipe(
-      getResponseFromEntry(),
-      map((response: RegistryBitstreamformatsSuccessResponse) => response.bitstreamformatsResponse)
-    );
-
-    const bitstreamformatsObs: Observable<BitstreamFormat[]> = rbrObs.pipe(
-      map((rbr: RegistryBitstreamformatsResponse) => rbr.bitstreamformats)
-    );
-
-    const pageInfoObs: Observable<PageInfo> = requestEntryObs.pipe(
-      getResponseFromEntry(),
-      map((response: RegistryBitstreamformatsSuccessResponse) => response.pageInfo)
-    );
-
-    const payloadObs = observableCombineLatest(bitstreamformatsObs, pageInfoObs).pipe(
-      map(([bitstreamformats, pageInfo]) => {
-        return new PaginatedList(pageInfo, bitstreamformats);
-      })
-    );
-
-    return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs);
-  }
-
-  private getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
+  public getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
     return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
       map((url: string) => {
         const args: string[] = [];
@@ -327,30 +288,6 @@ export class RegistryService {
     );
   }
 
-  private getBitstreamFormatsRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
-    return this.halService.getEndpoint(this.bitstreamFormatsPath).pipe(
-      map((url: string) => {
-        const args: string[] = [];
-        args.push(`size=${pagination.pageSize}`);
-        args.push(`page=${pagination.currentPage - 1}`);
-        if (isNotEmpty(args)) {
-          url = new URLCombiner(url, `?${args.join('&')}`).toString();
-        }
-        const request = new GetRequest(this.requestService.generateRequestId(), url);
-        return Object.assign(request, {
-          getResponseParser(): GenericConstructor<ResponseParsingService> {
-            return RegistryBitstreamformatsResponseParsingService;
-          }
-        });
-      }),
-      tap((request: RestRequest) => this.requestService.configure(request)),
-    );
-  }
-
-  /**
-   * Method to start editing a metadata schema, dispatches an edit schema action
-   * @param schema The schema that's being edited
-   */
   public editMetadataSchema(schema: MetadataSchema) {
     this.store.dispatch(new MetadataRegistryEditSchemaAction(schema));
   }
@@ -374,7 +311,7 @@ export class RegistryService {
    * @param schema The schema that's being selected
    */
   public selectMetadataSchema(schema: MetadataSchema) {
-    this.store.dispatch(new MetadataRegistrySelectSchemaAction(schema))
+    this.store.dispatch(new MetadataRegistrySelectSchemaAction(schema));
   }
 
   /**
@@ -382,14 +319,14 @@ export class RegistryService {
    * @param schema The schema that's it being deselected
    */
   public deselectMetadataSchema(schema: MetadataSchema) {
-    this.store.dispatch(new MetadataRegistryDeselectSchemaAction(schema))
+    this.store.dispatch(new MetadataRegistryDeselectSchemaAction(schema));
   }
 
   /**
    * Method to deselect all currently selected metadata schema, dispatches a deselect all schema action
    */
   public deselectAllMetadataSchema() {
-    this.store.dispatch(new MetadataRegistryDeselectAllSchemaAction())
+    this.store.dispatch(new MetadataRegistryDeselectAllSchemaAction());
   }
 
   /**
@@ -423,20 +360,20 @@ export class RegistryService {
    * @param field The field that's being selected
    */
   public selectMetadataField(field: MetadataField) {
-    this.store.dispatch(new MetadataRegistrySelectFieldAction(field))
+    this.store.dispatch(new MetadataRegistrySelectFieldAction(field));
   }
   /**
    * Method to deselect a metadata field, dispatches a deselect field action
    * @param field The field that's it being deselected
    */
   public deselectMetadataField(field: MetadataField) {
-    this.store.dispatch(new MetadataRegistryDeselectFieldAction(field))
+    this.store.dispatch(new MetadataRegistryDeselectFieldAction(field));
   }
   /**
    * Method to deselect all currently selected metadata fields, dispatches a deselect all field action
    */
   public deselectAllMetadataField() {
-    this.store.dispatch(new MetadataRegistryDeselectAllFieldAction())
+    this.store.dispatch(new MetadataRegistryDeselectAllFieldAction());
   }
 
   /**
@@ -494,7 +431,7 @@ export class RegistryService {
             this.notificationsService.error('Server Error:', (response as any).errorMessage, new NotificationOptions(-1));
           }
         } else {
-          this.showNotifications(true, isUpdate, false, { prefix: schema.prefix });
+          this.showNotifications(true, isUpdate, false, {prefix: schema.prefix});
           return response;
         }
       }),
@@ -521,7 +458,7 @@ export class RegistryService {
   public clearMetadataSchemaRequests(): Observable<string> {
     return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
       tap((href: string) => this.requestService.removeByHrefSubstring(href))
-    )
+    );
   }
 
   /**
@@ -571,7 +508,7 @@ export class RegistryService {
           }
         } else {
           const fieldString = `${field.schema.prefix}.${field.element}${field.qualifier ? `.${field.qualifier}` : ''}`;
-          this.showNotifications(true, isUpdate, true, { field: fieldString });
+          this.showNotifications(true, isUpdate, true, {field: fieldString});
           return response;
         }
       }),
@@ -597,7 +534,7 @@ export class RegistryService {
   public clearMetadataFieldRequests(): Observable<string> {
     return this.halService.getEndpoint(this.metadataFieldsPath).pipe(
       tap((href: string) => this.requestService.removeByHrefSubstring(href))
-    )
+    );
   }
 
   private delete(path: string, id: number): Observable<RestResponse> {
@@ -633,9 +570,9 @@ export class RegistryService {
     );
     messages.subscribe(([head, content]) => {
       if (success) {
-        this.notificationsService.success(head, content)
+        this.notificationsService.success(head, content);
       } else {
-        this.notificationsService.error(head, content)
+        this.notificationsService.error(head, content);
       }
     });
   }
diff --git a/src/app/core/shared/bitstream-format-support-level.ts b/src/app/core/shared/bitstream-format-support-level.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d92aac7708368c649532e4a678f30408a2d062c0
--- /dev/null
+++ b/src/app/core/shared/bitstream-format-support-level.ts
@@ -0,0 +1,5 @@
+export enum BitstreamFormatSupportLevel {
+  Known = 'KNOWN',
+  Unknown = 'UNKNOWN',
+  Supported = 'SUPPORTED'
+}
diff --git a/src/app/core/shared/bitstream-format.model.ts b/src/app/core/shared/bitstream-format.model.ts
index bf50cd832f552962fca735319c4bd2784855cc57..0e1279e97844bed638ff6b677cf9bbb6ce783c9f 100644
--- a/src/app/core/shared/bitstream-format.model.ts
+++ b/src/app/core/shared/bitstream-format.model.ts
@@ -1,6 +1,6 @@
-
 import { CacheableObject, TypedObject } from '../cache/object-cache.reducer';
 import { ResourceType } from './resource-type';
+import { BitstreamFormatSupportLevel } from './bitstream-format-support-level';
 
 /**
  * Model class for a Bitstream Format
@@ -27,7 +27,7 @@ export class BitstreamFormat implements CacheableObject {
   /**
    * The level of support the system offers for this Bitstream Format
    */
-  supportLevel: number;
+  supportLevel: BitstreamFormatSupportLevel;
 
   /**
    * True if the Bitstream Format is used to store system information, rather than the content of items in the system
@@ -37,7 +37,7 @@ export class BitstreamFormat implements CacheableObject {
   /**
    * String representing this Bitstream Format's file extension
    */
-  extensions: string;
+  extensions: string[];
 
   /**
    * The link to the rest endpoint where this Bitstream Format can be found
@@ -49,4 +49,11 @@ export class BitstreamFormat implements CacheableObject {
    */
   uuid: string;
 
+  /**
+   * Identifier for this Bitstream Format
+   * Note that this ID is unique for bitstream formats,
+   * but might not be unique across different object types
+   */
+  id: string;
+
 }
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..4cb34a140b179b0161a5cf6632e7e1a7831aab78
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html
@@ -0,0 +1,30 @@
+<ds-truncatable [id]="dso.id">
+  <div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
+    <a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
+      <div>
+        <ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
+        </ds-grid-thumbnail>
+      </div>
+    </a>
+    <div class="card-body">
+      <ds-item-type-badge [object]="object"></ds-item-type-badge>
+      <ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
+        <h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
+      </ds-truncatable-part>
+      <p *ngIf="dso.hasMetadata('creativework.datePublished')" class="item-date card-text text-muted">
+        <ds-truncatable-part [id]="dso.id" [minLines]="1">
+          <span [innerHTML]="firstMetadataValue('creativework.datePublished')"></span>
+        </ds-truncatable-part>
+      </p>
+      <p *ngIf="dso.hasMetadata('journal.title')" class="item-journal-title card-text">
+        <ds-truncatable-part [id]="dso.id" [minLines]="3">
+          <span [innerHTML]="firstMetadataValue('journal.title')"></span>
+        </ds-truncatable-part>
+      </p>
+      <div class="text-center">
+        <a [routerLink]="['/items/' + dso.id]"
+           class="lead btn btn-primary viewButton">View</a>
+      </div>
+    </div>
+  </div>
+</ds-truncatable>
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..68a05b66c3cf2c84fc918be1056044a03a3a583a
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts
@@ -0,0 +1,47 @@
+import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
+import { JournalIssueGridElementComponent } from './journal-issue-grid-element.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ],
+    'creativework.datePublished': [
+      {
+        language: null,
+        value: '2015-06-26'
+      }
+    ],
+    'journal.title': [
+      {
+        language: 'en_US',
+        value: 'The journal title'
+      }
+    ]
+  }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ]
+  }
+});
+
+describe('JournalIssueGridElementComponent', getEntityGridElementTestComponent(JournalIssueGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'journal-title']));
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..06c27ebacf60ab6cc05b64739a174d0f3a19b2e5
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
+import { Component } from '@angular/core';
+import { focusShadow } from '../../../../shared/animations/focus';
+import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
+
+@rendersItemType('JournalIssue', ItemViewMode.Card)
+@Component({
+  selector: 'ds-journal-issue-grid-element',
+  styleUrls: ['./journal-issue-grid-element.component.scss'],
+  templateUrl: './journal-issue-grid-element.component.html',
+  animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item of the type Journal Issue
+ */
+export class JournalIssueGridElementComponent extends TypedItemSearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..d7c9b68a24e92f25e7fcd9136bf27e0435a39a55
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html
@@ -0,0 +1,30 @@
+<ds-truncatable [id]="dso.id">
+  <div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
+    <a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
+      <div>
+        <ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
+        </ds-grid-thumbnail>
+      </div>
+    </a>
+    <div class="card-body">
+      <ds-item-type-badge [object]="object"></ds-item-type-badge>
+      <ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
+        <h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
+      </ds-truncatable-part>
+      <p *ngIf="dso.hasMetadata('creativework.datePublished')" class="item-date card-text text-muted">
+        <ds-truncatable-part [id]="dso.id" [minLines]="1">
+          <span [innerHTML]="firstMetadataValue('creativework.datePublished')"></span>
+        </ds-truncatable-part>
+      </p>
+      <p *ngIf="dso.hasMetadata('dc.description')" class="item-description card-text">
+        <ds-truncatable-part [id]="dso.id" [minLines]="3">
+          <span [innerHTML]="firstMetadataValue('dc.description')"></span>
+        </ds-truncatable-part>
+      </p>
+      <div class="text-center">
+        <a [routerLink]="['/items/' + dso.id]"
+           class="lead btn btn-primary viewButton">View</a>
+      </div>
+    </div>
+  </div>
+</ds-truncatable>
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5a8fca5fc689626c5a96bd56ec144d06d0e21cdd
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts
@@ -0,0 +1,47 @@
+import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
+import { JournalVolumeGridElementComponent } from './journal-volume-grid-element.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ],
+    'creativework.datePublished': [
+      {
+        language: null,
+        value: '2015-06-26'
+      }
+    ],
+    'dc.description': [
+      {
+        language: 'en_US',
+        value: 'A description for the journal volume'
+      }
+    ]
+  }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ]
+  }
+});
+
+describe('JournalVolumeGridElementComponent', getEntityGridElementTestComponent(JournalVolumeGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'description']));
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e5183536ef710c636079cda85e4b30884bea0079
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
+import { Component } from '@angular/core';
+import { focusShadow } from '../../../../shared/animations/focus';
+import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
+
+@rendersItemType('JournalVolume', ItemViewMode.Card)
+@Component({
+  selector: 'ds-journal-volume-grid-element',
+  styleUrls: ['./journal-volume-grid-element.component.scss'],
+  templateUrl: './journal-volume-grid-element.component.html',
+  animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item of the type Journal Volume
+ */
+export class JournalVolumeGridElementComponent extends TypedItemSearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..467cdd15941084097624c03d0911608373e26538
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html
@@ -0,0 +1,35 @@
+<ds-truncatable [id]="dso.id">
+  <div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
+    <a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
+      <div>
+        <ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
+        </ds-grid-thumbnail>
+      </div>
+    </a>
+    <div class="card-body">
+      <ds-item-type-badge [object]="object"></ds-item-type-badge>
+      <ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
+        <h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
+      </ds-truncatable-part>
+      <p *ngIf="dso.hasMetadata('creativework.editor')"
+         class="item-publisher card-text text-muted">
+        <ds-truncatable-part [id]="dso.id" [minLines]="1">
+          <span class="item-editor">{{dso.firstMetadataValue('creativework.editor')}}</span>
+          <span *ngIf="dso.hasMetadata('creativework.publisher')" class="item-publisher">
+            <span>, </span>
+            {{dso.firstMetadataValue('creativework.publisher')}}
+          </span>
+        </ds-truncatable-part>
+      </p>
+      <p *ngIf="dso.hasMetadata('dc.description')" class="item-description card-text">
+        <ds-truncatable-part [id]="dso.id" [minLines]="3">
+          <span [innerHTML]="firstMetadataValue('dc.description')"></span>
+        </ds-truncatable-part>
+      </p>
+      <div class="text-center">
+        <a [routerLink]="['/items/' + dso.id]"
+           class="lead btn btn-primary viewButton">View</a>
+      </div>
+    </div>
+  </div>
+</ds-truncatable>
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8c12c1a2667c07838ce04afa0e77165d91d33ee3
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts
@@ -0,0 +1,53 @@
+import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
+import { JournalGridElementComponent } from './journal-grid-element.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ],
+    'creativework.editor': [
+      {
+        language: 'en_US',
+        value: 'Smith, Donald'
+      }
+    ],
+    'creativework.publisher': [
+      {
+        language: 'en_US',
+        value: 'A company'
+      }
+    ],
+    'dc.description': [
+      {
+        language: 'en_US',
+        value: 'This is the description'
+      }
+    ]
+  }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ]
+  }
+});
+
+describe('JournalGridElementComponent', getEntityGridElementTestComponent(JournalGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['editor', 'publisher', 'description']));
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7f23211538ec1c3e0035a18b0edec8d48f83c96c
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
+import { Component } from '@angular/core';
+import { focusShadow } from '../../../../shared/animations/focus';
+import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
+
+@rendersItemType('Journal', ItemViewMode.Card)
+@Component({
+  selector: 'ds-journal-grid-element',
+  styleUrls: ['./journal-grid-element.component.scss'],
+  templateUrl: './journal-grid-element.component.html',
+  animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item of the type Journal
+ */
+export class JournalGridElementComponent extends TypedItemSearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/journal-entities/journal-entities.module.ts b/src/app/entity-groups/journal-entities/journal-entities.module.ts
index 50ec1606509b91fbcfb51f70bae6834216b99f53..4033645e1b32a321801f3986db8fc4d78279621e 100644
--- a/src/app/entity-groups/journal-entities/journal-entities.module.ts
+++ b/src/app/entity-groups/journal-entities/journal-entities.module.ts
@@ -9,6 +9,9 @@ import { JournalListElementComponent } from './item-list-elements/journal/journa
 import { JournalIssueListElementComponent } from './item-list-elements/journal-issue/journal-issue-list-element.component';
 import { JournalVolumeListElementComponent } from './item-list-elements/journal-volume/journal-volume-list-element.component';
 import { TooltipModule } from 'ngx-bootstrap';
+import { JournalIssueGridElementComponent } from './item-grid-elements/journal-issue/journal-issue-grid-element.component';
+import { JournalVolumeGridElementComponent } from './item-grid-elements/journal-volume/journal-volume-grid-element.component';
+import { JournalGridElementComponent } from './item-grid-elements/journal/journal-grid-element.component';
 
 const ENTRY_COMPONENTS = [
   JournalComponent,
@@ -16,7 +19,10 @@ const ENTRY_COMPONENTS = [
   JournalVolumeComponent,
   JournalListElementComponent,
   JournalIssueListElementComponent,
-  JournalVolumeListElementComponent
+  JournalVolumeListElementComponent,
+  JournalIssueGridElementComponent,
+  JournalVolumeGridElementComponent,
+  JournalGridElementComponent
 ];
 
 @NgModule({
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..104d3a0a579d1bc46c05e35e2f5d537d10623297
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html
@@ -0,0 +1,35 @@
+<ds-truncatable [id]="dso.id">
+  <div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
+    <a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
+      <div>
+        <ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
+        </ds-grid-thumbnail>
+      </div>
+    </a>
+    <div class="card-body">
+      <ds-item-type-badge [object]="object"></ds-item-type-badge>
+      <ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
+        <h4 class="card-title" [innerHTML]="dso.firstMetadataValue('organization.legalName')"></h4>
+      </ds-truncatable-part>
+      <p *ngIf="dso.hasMetadata('organization.foundingDate')" class="item-date card-text text-muted">
+        <ds-truncatable-part [id]="dso.id" [minLines]="1">
+          <span [innerHTML]="firstMetadataValue('organization.foundingDate')"></span>
+        </ds-truncatable-part>
+      </p>
+      <p *ngIf="dso.hasMetadata('organization.address.addressCountry')"
+         class="item-location card-text">
+        <ds-truncatable-part [id]="dso.id" [minLines]="3">
+          <span class="item-country">{{dso.firstMetadataValue('organization.address.addressCountry')}}</span>
+          <span *ngIf="dso.hasMetadata('organization.address.addressLocality')" class="item-city">
+            <span>, </span>
+            {{dso.firstMetadataValue('organization.address.addressLocality')}}
+          </span>
+        </ds-truncatable-part>
+      </p>
+      <div class="text-center">
+        <a [routerLink]="['/items/' + dso.id]"
+           class="lead btn btn-primary viewButton">View</a>
+      </div>
+    </div>
+  </div>
+</ds-truncatable>
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..39ddea4c7bbcfb5ca29f2d07aae67cb66229f150
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts
@@ -0,0 +1,53 @@
+import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
+import { OrgunitGridElementComponent } from './orgunit-grid-element.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ],
+    'organization.foundingDate': [
+      {
+        language: null,
+        value: '2015-06-26'
+      }
+    ],
+    'organization.address.addressCountry': [
+      {
+        language: 'en_US',
+        value: 'Belgium'
+      }
+    ],
+    'organization.address.addressLocality': [
+      {
+        language: 'en_US',
+        value: 'Brussels'
+      }
+    ]
+  }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ]
+  }
+});
+
+describe('OrgunitGridElementComponent', getEntityGridElementTestComponent(OrgunitGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'country', 'city']));
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0effc22027dba70cd985abad0029882c5f50e7d5
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
+import { Component } from '@angular/core';
+import { focusShadow } from '../../../../shared/animations/focus';
+import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
+
+@rendersItemType('OrgUnit', ItemViewMode.Card)
+@Component({
+  selector: 'ds-orgunit-grid-element',
+  styleUrls: ['./orgunit-grid-element.component.scss'],
+  templateUrl: './orgunit-grid-element.component.html',
+  animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item of the type Organisation Unit
+ */
+export class OrgunitGridElementComponent extends TypedItemSearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..86353377fa79b253e00fbdfa5a1c06798a61e4f1
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html
@@ -0,0 +1,30 @@
+<ds-truncatable [id]="dso.id">
+  <div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
+    <a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
+      <div>
+        <ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
+        </ds-grid-thumbnail>
+      </div>
+    </a>
+    <div class="card-body">
+      <ds-item-type-badge [object]="object"></ds-item-type-badge>
+      <ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
+        <h4 class="card-title" [innerHTML]="dso.firstMetadataValue('person.familyName') + ', ' + dso.firstMetadataValue('person.givenName')"></h4>
+      </ds-truncatable-part>
+      <p *ngIf="dso.hasMetadata('person.email')" class="item-email card-text text-muted">
+        <ds-truncatable-part [id]="dso.id" [minLines]="1">
+          <span [innerHTML]="firstMetadataValue('person.email')"></span>
+        </ds-truncatable-part>
+      </p>
+      <p *ngIf="dso.hasMetadata('person.jobTitle')" class="item-jobtitle card-text">
+        <ds-truncatable-part [id]="dso.id" [minLines]="3">
+          <span [innerHTML]="firstMetadataValue('person.jobTitle')"></span>
+        </ds-truncatable-part>
+      </p>
+      <div class="text-center">
+        <a [routerLink]="['/items/' + dso.id]"
+           class="lead btn btn-primary viewButton">View</a>
+      </div>
+    </div>
+  </div>
+</ds-truncatable>
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a0f8e4c29e61541058433783c43a18fba7825435
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts
@@ -0,0 +1,47 @@
+import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
+import { PersonGridElementComponent } from './person-grid-element.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ],
+    'person.email': [
+      {
+        language: 'en_US',
+        value: 'Smith-Donald@gmail.com'
+      }
+    ],
+    'person.jobTitle': [
+      {
+        language: 'en_US',
+        value: 'Web Developer'
+      }
+    ]
+  }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ]
+  }
+});
+
+describe('PersonGridElementComponent', getEntityGridElementTestComponent(PersonGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['email', 'jobtitle']));
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bf7b8aa119ea3f58406ffe0c38c5d424481e1791
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
+import { Component } from '@angular/core';
+import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
+import { focusShadow } from '../../../../shared/animations/focus';
+
+@rendersItemType('Person', ItemViewMode.Card)
+@Component({
+  selector: 'ds-person-grid-element',
+  styleUrls: ['./person-grid-element.component.scss'],
+  templateUrl: './person-grid-element.component.html',
+  animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item of the type Person
+ */
+export class PersonGridElementComponent extends TypedItemSearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..a595791cc42e2d5b7c6ab4d4e52997a397e99d01
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html
@@ -0,0 +1,25 @@
+<ds-truncatable [id]="dso.id">
+  <div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
+    <a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
+      <div>
+        <ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
+        </ds-grid-thumbnail>
+      </div>
+    </a>
+    <div class="card-body">
+      <ds-item-type-badge [object]="object"></ds-item-type-badge>
+      <ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
+        <h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
+      </ds-truncatable-part>
+      <p *ngIf="dso.hasMetadata('dc.description')" class="item-description card-text text-muted">
+        <ds-truncatable-part [id]="dso.id" [minLines]="3">
+          <span [innerHTML]="firstMetadataValue('dc.description')"></span>
+        </ds-truncatable-part>
+      </p>
+      <div class="text-center">
+        <a [routerLink]="['/items/' + dso.id]"
+           class="lead btn btn-primary viewButton">View</a>
+      </div>
+    </div>
+  </div>
+</ds-truncatable>
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9ad26935b768c5768ca6858f996f9d26939c5113
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts
@@ -0,0 +1,41 @@
+import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
+import { ProjectGridElementComponent } from './project-grid-element.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ],
+    'dc.description': [
+      {
+        language: 'en_US',
+        value: 'The project description'
+      }
+    ]
+  }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ]
+  }
+});
+
+describe('ProjectGridElementComponent', getEntityGridElementTestComponent(ProjectGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['description']));
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..15d525fcf2c46afc87171e93fd97b18538e1fe69
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
+import { Component } from '@angular/core';
+import { focusShadow } from '../../../../shared/animations/focus';
+import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
+
+@rendersItemType('Project', ItemViewMode.Card)
+@Component({
+  selector: 'ds-project-grid-element',
+  styleUrls: ['./project-grid-element.component.scss'],
+  templateUrl: './project-grid-element.component.html',
+  animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item of the type Project
+ */
+export class ProjectGridElementComponent extends TypedItemSearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/research-entities/research-entities.module.ts b/src/app/entity-groups/research-entities/research-entities.module.ts
index ba28f174df8c243afaab16610e93874f7e7c3bdc..099fa2a6a346eae4f22c3409b5b3d057891b9f72 100644
--- a/src/app/entity-groups/research-entities/research-entities.module.ts
+++ b/src/app/entity-groups/research-entities/research-entities.module.ts
@@ -11,6 +11,9 @@ import { PersonMetadataListElementComponent } from './item-list-elements/person/
 import { PersonListElementComponent } from './item-list-elements/person/person-list-element.component';
 import { ProjectListElementComponent } from './item-list-elements/project/project-list-element.component';
 import { TooltipModule } from 'ngx-bootstrap';
+import { PersonGridElementComponent } from './item-grid-elements/person/person-grid-element.component';
+import { OrgunitGridElementComponent } from './item-grid-elements/orgunit/orgunit-grid-element.component';
+import { ProjectGridElementComponent } from './item-grid-elements/project/project-grid-element.component';
 
 const ENTRY_COMPONENTS = [
   OrgunitComponent,
@@ -20,7 +23,10 @@ const ENTRY_COMPONENTS = [
   OrgUnitMetadataListElementComponent,
   PersonListElementComponent,
   PersonMetadataListElementComponent,
-  ProjectListElementComponent
+  ProjectListElementComponent,
+  PersonGridElementComponent,
+  OrgunitGridElementComponent,
+  ProjectGridElementComponent
 ];
 
 @NgModule({
diff --git a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts
index 6a96892b06cd8a587446512086535e2443b9c877..8d1d5c1dcabf1bebd01f2e3f0a43ae2ce6acc96c 100644
--- a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts
+++ b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts
@@ -9,6 +9,7 @@ import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynami
 import { TranslateService } from '@ngx-translate/core';
 import { DSpaceObject } from '../../../core/shared/dspace-object.model';
 import { MetadataMap, MetadataValue } from '../../../core/shared/metadata.models';
+import { ResourceType } from '../../../core/shared/resource-type';
 import { isNotEmpty } from '../../empty.util';
 import { Community } from '../../../core/shared/community.model';
 
@@ -29,7 +30,7 @@ export class ComColFormComponent<T extends DSpaceObject> implements OnInit {
   /**
    * Type of DSpaceObject that the form represents
    */
-  protected type;
+  protected type: ResourceType;
 
   /**
    * @type {string} Key prefix used to generate form labels
@@ -110,11 +111,11 @@ export class ComColFormComponent<T extends DSpaceObject> implements OnInit {
   private updateFieldTranslations() {
     this.formModel.forEach(
       (fieldModel: DynamicInputModel) => {
-        fieldModel.label = this.translate.instant(this.type + this.LABEL_KEY_PREFIX + fieldModel.id);
+        fieldModel.label = this.translate.instant(this.type.value + this.LABEL_KEY_PREFIX + fieldModel.id);
         if (isNotEmpty(fieldModel.validators)) {
           fieldModel.errorMessages = {};
           Object.keys(fieldModel.validators).forEach((key) => {
-            fieldModel.errorMessages[key] = this.translate.instant(this.type + this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key);
+            fieldModel.errorMessages[key] = this.translate.instant(this.type.value + this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key);
           });
         }
       }
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
index 5692c27d207cca80a28dddae93554bae4904bc11..52a924604f90f4bb4d0b27776db0022391663df6 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
@@ -15,7 +15,7 @@
       <ng-container #componentViewContainer></ng-container>
 
       <small *ngIf="hasHint && (!showErrorMessages || errorMessages.length === 0)"
-             class="text-muted" [innerHTML]="model.hint" [ngClass]="getClass('element', 'hint')"></small>
+             class="text-muted" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
 
       <div *ngIf="showErrorMessages" [ngClass]="[getClass('element', 'errors'), getClass('grid', 'errors')]">
         <small *ngFor="let message of errorMessages" class="invalid-feedback d-block">{{ message | translate:model.validators }}</small>
diff --git a/src/app/shared/items/item-type-decorator.ts b/src/app/shared/items/item-type-decorator.ts
index 2420e719087a7b876bb0745a7de3f2143f9e548c..3a040ae5bfd70bf3f4e01b576d4eb0e750a2c7fd 100644
--- a/src/app/shared/items/item-type-decorator.ts
+++ b/src/app/shared/items/item-type-decorator.ts
@@ -3,6 +3,7 @@ import { MetadataRepresentationType } from '../../core/shared/metadata-represent
 
 export enum ItemViewMode {
   Element = 'element',
+  Card = 'card',
   Full = 'full',
   Metadata = 'metadata'
 }
diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html
index c0c3c1f65fe1473593170c5a54a0a8eaa92a80d3..5b09d09a55daab41785bd318ae16b1c1e501bcb8 100644
--- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html
+++ b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html
@@ -1,4 +1,3 @@
 <div class="thumbnail">
-  <img *ngIf="thumbnail && thumbnail.content" [src]="thumbnail.content" (error)="errorHandler($event)"/>
-  <img *ngIf="!(thumbnail && thumbnail.content)" [src]="holderSource | dsSafeUrl"/>
+  <img [src]="src | dsSafeUrl" (error)="errorHandler($event)"/>
 </div>
diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts
index 2d2bd6305a8fda6143d1723fa030d5ce00765b20..170ca34b42ed206a0b14fe766607264e8acbe632 100644
--- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts
+++ b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts
@@ -6,7 +6,7 @@ import { GridThumbnailComponent } from './grid-thumbnail.component';
 import { Bitstream } from '../../../core/shared/bitstream.model';
 import { SafeUrlPipe } from '../../utils/safe-url-pipe';
 
-describe('ThumbnailComponent', () => {
+describe('GridThumbnailComponent', () => {
   let comp: GridThumbnailComponent;
   let fixture: ComponentFixture<GridThumbnailComponent>;
   let de: DebugElement;
@@ -36,7 +36,7 @@ describe('ThumbnailComponent', () => {
   it('should display placeholder', () => {
     fixture.detectChanges();
     const image: HTMLElement = de.query(By.css('img')).nativeElement;
-    expect(image.getAttribute('src')).toBe(comp.holderSource);
+    expect(image.getAttribute('src')).toBe(comp.defaultImage);
   });
 
 });
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 8ca93470da4dd680f2e16d76925b8e86fbee2b00..6ae0c2d37eb414ff220252d4fe9d21e6c857e5e7 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
@@ -1,5 +1,6 @@
-import { Component, Input } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
 import { Bitstream } from '../../../core/shared/bitstream.model';
+import { hasValue } from '../../empty.util';
 
 /**
  * This component renders a given Bitstream as a thumbnail.
@@ -12,7 +13,7 @@ import { Bitstream } from '../../../core/shared/bitstream.model';
   styleUrls: ['./grid-thumbnail.component.scss'],
   templateUrl: './grid-thumbnail.component.html'
 })
-export class GridThumbnailComponent {
+export class GridThumbnailComponent implements OnInit {
 
   @Input() thumbnail: Bitstream;
 
@@ -21,10 +22,19 @@ export class GridThumbnailComponent {
   /**
    * The default 'holder.js' image
    */
-  holderSource = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjYwIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDI2MCAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjwhLS0KU291cmNlIFVSTDogaG9sZGVyLmpzLzEwMCV4MTgwL3RleHQ6Tm8gVGh1bWJuYWlsCkNyZWF0ZWQgd2l0aCBIb2xkZXIuanMgMi42LjAuCkxlYXJuIG1vcmUgYXQgaHR0cDovL2hvbGRlcmpzLmNvbQooYykgMjAxMi0yMDE1IEl2YW4gTWFsb3BpbnNreSAtIGh0dHA6Ly9pbXNreS5jbwotLT48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWyNob2xkZXJfMTVmNzJmMmFlMGIgdGV4dCB7IGZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxM3B0IH0gXV0+PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImhvbGRlcl8xNWY3MmYyYWUwYiI+PHJlY3Qgd2lkdGg9IjI2MCIgaGVpZ2h0PSIxODAiIGZpbGw9IiNFRUVFRUUiLz48Zz48dGV4dCB4PSI3Mi4yNDIxODc1IiB5PSI5NiI+Tm8gVGh1bWJuYWlsPC90ZXh0PjwvZz48L2c+PC9zdmc+';
+  @Input() defaultImage? = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjYwIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDI2MCAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjwhLS0KU291cmNlIFVSTDogaG9sZGVyLmpzLzEwMCV4MTgwL3RleHQ6Tm8gVGh1bWJuYWlsCkNyZWF0ZWQgd2l0aCBIb2xkZXIuanMgMi42LjAuCkxlYXJuIG1vcmUgYXQgaHR0cDovL2hvbGRlcmpzLmNvbQooYykgMjAxMi0yMDE1IEl2YW4gTWFsb3BpbnNreSAtIGh0dHA6Ly9pbXNreS5jbwotLT48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWyNob2xkZXJfMTVmNzJmMmFlMGIgdGV4dCB7IGZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxM3B0IH0gXV0+PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImhvbGRlcl8xNWY3MmYyYWUwYiI+PHJlY3Qgd2lkdGg9IjI2MCIgaGVpZ2h0PSIxODAiIGZpbGw9IiNFRUVFRUUiLz48Zz48dGV4dCB4PSI3Mi4yNDIxODc1IiB5PSI5NiI+Tm8gVGh1bWJuYWlsPC90ZXh0PjwvZz48L2c+PC9zdmc+';
 
+  src: string;
   errorHandler(event) {
-    event.currentTarget.src = this.holderSource;
+    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/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..e2477524cad43f80184d9f1136616ba258cf8b7f
--- /dev/null
+++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html
@@ -0,0 +1,34 @@
+<ds-truncatable [id]="dso.id">
+  <div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
+    <a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
+      <div>
+        <ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
+        </ds-grid-thumbnail>
+      </div>
+    </a>
+    <div class="card-body">
+      <ds-item-type-badge [object]="object"></ds-item-type-badge>
+      <ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
+        <h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
+      </ds-truncatable-part>
+      <p *ngIf="dso.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])"
+         class="item-authors card-text text-muted">
+        <ds-truncatable-part [id]="dso.id" [minLines]="1">
+          <span *ngIf="dso.hasMetadata('dc.date.issued')" class="item-date">{{dso.firstMetadataValue('dc.date.issued')}}</span>
+          <span *ngFor="let author of dso.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);">,
+                        <span [innerHTML]="author"></span>
+                    </span>
+        </ds-truncatable-part>
+      </p>
+      <p *ngIf="dso.hasMetadata('dc.description.abstract')" class="item-abstract card-text">
+        <ds-truncatable-part [id]="dso.id" [minLines]="3">
+          <span [innerHTML]="firstMetadataValue('dc.description.abstract')"></span>
+        </ds-truncatable-part>
+      </p>
+      <div class="text-center">
+        <a [routerLink]="['/items/' + dso.id]"
+           class="lead btn btn-primary viewButton">View</a>
+      </div>
+    </div>
+  </div>
+</ds-truncatable>
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.scss b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f067a21ae0a295895df1536473fe9e97eb7db6db
--- /dev/null
+++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts
@@ -0,0 +1,124 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { TruncatePipe } from '../../../../utils/truncate.pipe';
+import { TruncatableService } from '../../../../truncatable/truncatable.service';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { PublicationGridElementComponent } from './publication-grid-element.component';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { ITEM } from '../../../../items/switcher/item-type-switcher.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ],
+    'dc.contributor.author': [
+      {
+        language: 'en_US',
+        value: 'Smith, Donald'
+      }
+    ],
+    'dc.date.issued': [
+      {
+        language: null,
+        value: '2015-06-26'
+      }
+    ],
+    'dc.description.abstract': [
+      {
+        language: 'en_US',
+        value: 'This is an abstract'
+      }
+    ]
+  }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ]
+  }
+});
+
+describe('PublicationGridElementComponent', getEntityGridElementTestComponent(PublicationGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['authors', 'date', 'abstract']));
+
+/**
+ * Create test cases for a grid component of an entity.
+ * @param component                     The component's class
+ * @param searchResultWithMetadata      An ItemSearchResult containing an item with metadata that should be displayed in the grid element
+ * @param searchResultWithoutMetadata   An ItemSearchResult containing an item that's missing the metadata that should be displayed in the grid element
+ * @param fieldsToCheck                 A list of fields to check. The tests expect to find html elements with class ".item-${field}", so make sure they exist in the html template of the grid element.
+ *                                      For example: If one of the fields to check is labeled "authors", the html template should contain at least one element with class ".item-authors" that's
+ *                                      present when the author metadata is available.
+ */
+export function getEntityGridElementTestComponent(component, searchResultWithMetadata: ItemSearchResult, searchResultWithoutMetadata: ItemSearchResult, fieldsToCheck: string[]) {
+  return () => {
+    let comp;
+    let fixture;
+
+    const truncatableServiceStub: any = {
+      isCollapsed: (id: number) => observableOf(true),
+    };
+
+    beforeEach(async(() => {
+      TestBed.configureTestingModule({
+        imports: [NoopAnimationsModule],
+        declarations: [component, TruncatePipe],
+        providers: [
+          { provide: TruncatableService, useValue: truncatableServiceStub },
+          {provide: ITEM, useValue: searchResultWithoutMetadata}
+        ],
+        schemas: [NO_ERRORS_SCHEMA]
+      }).overrideComponent(component, {
+        set: { changeDetection: ChangeDetectionStrategy.Default }
+      }).compileComponents();
+    }));
+
+    beforeEach(async(() => {
+      fixture = TestBed.createComponent(component);
+      comp = fixture.componentInstance;
+    }));
+
+    fieldsToCheck.forEach((field) => {
+      describe(`when the item has "${field}" metadata`, () => {
+        beforeEach(() => {
+          comp.dso = searchResultWithMetadata.indexableObject;
+          fixture.detectChanges();
+        });
+
+        it(`should show the "${field}" field`, () => {
+          const itemAuthorField = fixture.debugElement.query(By.css(`.item-${field}`));
+          expect(itemAuthorField).not.toBeNull();
+        });
+      });
+
+      describe(`when the item has no "${field}" metadata`, () => {
+        beforeEach(() => {
+          comp.dso = searchResultWithoutMetadata.indexableObject;
+          fixture.detectChanges();
+        });
+
+        it(`should not show the "${field}" field`, () => {
+          const itemAuthorField = fixture.debugElement.query(By.css(`.item-${field}`));
+          expect(itemAuthorField).toBeNull();
+        });
+      });
+    });
+  }
+}
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1bcd028baf31f741593513e399af9e585c935475
--- /dev/null
+++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts
@@ -0,0 +1,18 @@
+import { TypedItemSearchResultGridElementComponent } from '../typed-item-search-result-grid-element.component';
+import { DEFAULT_ITEM_TYPE, ItemViewMode, rendersItemType } from '../../../../items/item-type-decorator';
+import { Component } from '@angular/core';
+import { focusShadow } from '../../../../animations/focus';
+
+@rendersItemType('Publication', ItemViewMode.Card)
+@rendersItemType(DEFAULT_ITEM_TYPE, ItemViewMode.Card)
+@Component({
+  selector: 'ds-publication-grid-element',
+  styleUrls: ['./publication-grid-element.component.scss'],
+  templateUrl: './publication-grid-element.component.html',
+  animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item of the type Publication
+ */
+export class PublicationGridElementComponent extends TypedItemSearchResultGridElementComponent {
+}
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e4ace8d0b204fd7cf49ec26429deeb579c98de4c
--- /dev/null
+++ b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts
@@ -0,0 +1,83 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { TruncatePipe } from '../../../utils/truncate.pipe';
+import { TruncatableService } from '../../../truncatable/truncatable.service';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { Item } from '../../../../core/shared/item.model';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { PaginatedList } from '../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../core/shared/page-info.model';
+import { ITEM } from '../../../items/switcher/item-type-switcher.component';
+import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
+import { createRelationshipsObservable } from '../../../../+item-page/simple/item-types/shared/item.component.spec';
+import { of as observableOf } from 'rxjs';
+import { MetadataMap } from '../../../../core/shared/metadata.models';
+import { TypedItemSearchResultGridElementComponent } from './typed-item-search-result-grid-element.component';
+
+const mockItem: Item = Object.assign(new Item(), {
+  bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
+  metadata: [],
+  relationships: createRelationshipsObservable()
+});
+const mockSearchResult = {
+  indexableObject: mockItem as Item,
+  hitHighlights: new MetadataMap()
+} as ItemSearchResult;
+
+describe('TypedItemSearchResultGridElementComponent', () => {
+  let comp: TypedItemSearchResultGridElementComponent;
+  let fixture: ComponentFixture<TypedItemSearchResultGridElementComponent>;
+
+  describe('when injecting an Item', () => {
+    beforeEach(async(() => {
+      TestBed.configureTestingModule({
+        declarations: [TypedItemSearchResultGridElementComponent, TruncatePipe],
+        providers: [
+          {provide: TruncatableService, useValue: {}},
+          {provide: ITEM, useValue: mockItem}
+        ],
+
+        schemas: [NO_ERRORS_SCHEMA]
+      }).overrideComponent(TypedItemSearchResultGridElementComponent, {
+        set: {changeDetection: ChangeDetectionStrategy.Default}
+      }).compileComponents();
+    }));
+
+    beforeEach(async(() => {
+      fixture = TestBed.createComponent(TypedItemSearchResultGridElementComponent);
+      comp = fixture.componentInstance;
+    }));
+
+    it('should initiate item, object and dso correctly', () => {
+      expect(comp.item).toBe(mockItem);
+      expect(comp.dso).toBe(mockItem);
+      expect(comp.object.indexableObject).toBe(mockItem);
+    })
+  });
+
+  describe('when injecting an ItemSearchResult', () => {
+    beforeEach(async(() => {
+      TestBed.configureTestingModule({
+        declarations: [TypedItemSearchResultGridElementComponent, TruncatePipe],
+        providers: [
+          {provide: TruncatableService, useValue: {}},
+          {provide: ITEM, useValue: mockSearchResult}
+        ],
+
+        schemas: [NO_ERRORS_SCHEMA]
+      }).overrideComponent(TypedItemSearchResultGridElementComponent, {
+        set: {changeDetection: ChangeDetectionStrategy.Default}
+      }).compileComponents();
+    }));
+
+    beforeEach(async(() => {
+      fixture = TestBed.createComponent(TypedItemSearchResultGridElementComponent);
+      comp = fixture.componentInstance;
+    }));
+
+    it('should initiate item, object and dso correctly', () => {
+      expect(comp.item).toBe(mockItem);
+      expect(comp.dso).toBe(mockItem);
+      expect(comp.object.indexableObject).toBe(mockItem);
+    })
+  });
+});
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.ts b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f4f470c05271bfd8d2fe50273870c0108e956421
--- /dev/null
+++ b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.ts
@@ -0,0 +1,37 @@
+import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
+import { Item } from '../../../../core/shared/item.model';
+import { SearchResultGridElementComponent } from '../../search-result-grid-element/search-result-grid-element.component';
+import { TruncatableService } from '../../../truncatable/truncatable.service';
+import { Component, Inject } from '@angular/core';
+import { ITEM } from '../../../items/switcher/item-type-switcher.component';
+import { hasValue } from '../../../empty.util';
+import { MetadataMap } from '../../../../core/shared/metadata.models';
+
+/**
+ * A generic component for displaying item grid elements
+ */
+@Component({
+  selector: 'ds-item-search-result-grid-element',
+  template: ''
+})
+export class TypedItemSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> {
+  item: Item;
+
+  constructor(
+    protected truncatableService: TruncatableService,
+    @Inject(ITEM) public obj: Item | ItemSearchResult,
+  ) {
+    super(undefined, truncatableService);
+    if (hasValue((obj as any).indexableObject)) {
+      this.object = obj as ItemSearchResult;
+      this.dso = this.object.indexableObject;
+    } else {
+      this.object = {
+        indexableObject: obj as Item,
+        hitHighlights: new MetadataMap()
+      };
+      this.dso = obj as Item;
+    }
+    this.item = this.dso;
+  }
+}
diff --git a/src/app/shared/object-grid/object-grid.component.scss b/src/app/shared/object-grid/object-grid.component.scss
index 437dfc3b434eb47e586f54243a8f9baa06eb3e94..8f19309d89fd948d5597dcf5127610283ac03628 100644
--- a/src/app/shared/object-grid/object-grid.component.scss
+++ b/src/app/shared/object-grid/object-grid.component.scss
@@ -4,6 +4,11 @@ ds-wrapper-grid-element ::ng-deep {
   div.thumbnail > img {
     height: $card-thumbnail-height;
     width: 100%;
+    display: block;
+    min-width: 100%;
+    min-height: 100%;
+    object-fit: cover;
+    object-position: 50% 15%;
   }
   div.card {
     margin-top: $ds-wrapper-grid-spacing;
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html
index c7e2f524f389306a7c44d8c599a4b28f09b2c4f5..d433c7acf24ce47b1cc4c820e700795566529541 100644
--- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html
+++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html
@@ -1,33 +1 @@
-<ds-truncatable [id]="dso.id">
-    <div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
-        <a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
-            <div>
-                <ds-grid-thumbnail [thumbnail]="dso.getThumbnail()">
-                </ds-grid-thumbnail>
-            </div>
-        </a>
-        <div class="card-body">
-            <ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="3" type="h4">
-                <h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
-            </ds-truncatable-part>
-            <p *ngIf="dso.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])"
-               class="item-authors card-text text-muted">
-                <ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="1">
-                    <span *ngIf="dso.hasMetadata('dc.date.issued')" class="item-date">{{dso.firstMetadataValue('dc.date.issued')}}</span>
-                    <span *ngFor="let author of dso.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);">,
-                        <span [innerHTML]="author"></span>
-                    </span>
-                </ds-truncatable-part>
-            </p>
-            <p class="item-abstract card-text">
-                <ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="3">
-                    <span [innerHTML]="firstMetadataValue('dc.description.abstract')"></span>
-                </ds-truncatable-part>
-            </p>
-            <div class="text-center">
-                <a [routerLink]="['/items/' + dso.id]"
-                   class="lead btn btn-primary viewButton">View</a>
-            </div>
-        </div>
-    </div>
-</ds-truncatable>
+<ds-item-type-switcher [object]="object" [viewMode]="viewMode"></ds-item-type-switcher>
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts
index 655fd268a75d786d09a751e2841fb5286ab47c67..282478ec33c4ed083b3978e57515bec5de318647 100644
--- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts
+++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts
@@ -8,6 +8,7 @@ import { Item } from '../../../../core/shared/item.model';
 import { TruncatableService } from '../../../truncatable/truncatable.service';
 import { NoopAnimationsModule } from '@angular/platform-browser/animations';
 import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
+import { ItemViewMode } from '../../../items/item-type-decorator';
 
 let itemSearchResultGridElementComponent: ItemSearchResultGridElementComponent;
 let fixture: ComponentFixture<ItemSearchResultGridElementComponent>;
@@ -16,41 +17,17 @@ const truncatableServiceStub: any = {
   isCollapsed: (id: number) => observableOf(true),
 };
 
-const mockItemWithAuthorAndDate: ItemSearchResult = new ItemSearchResult();
-mockItemWithAuthorAndDate.hitHighlights = {};
-mockItemWithAuthorAndDate.indexableObject = Object.assign(new Item(), {
-  bitstreams: observableOf({}),
-  metadata: {
-    'dc.contributor.author': [
-      {
-        language: 'en_US',
-        value: 'Smith, Donald'
-      }
-    ],
-    'dc.date.issued': [
-      {
-        language: null,
-        value: '2015-06-26'
-      }
-    ]
-  }
-});
+const type = 'authorOfPublication';
 
-const mockItemWithoutAuthorAndDate: ItemSearchResult = new ItemSearchResult();
-mockItemWithoutAuthorAndDate.hitHighlights = {};
-mockItemWithoutAuthorAndDate.indexableObject = Object.assign(new Item(), {
+const mockItemWithRelationshipType: ItemSearchResult = new ItemSearchResult();
+mockItemWithRelationshipType.hitHighlights = {};
+mockItemWithRelationshipType.indexableObject = Object.assign(new Item(), {
   bitstreams: observableOf({}),
   metadata: {
-    'dc.title': [
+    'relationship.type': [
       {
         language: 'en_US',
-        value: 'This is just another title'
-      }
-    ],
-    'dc.type': [
-      {
-        language: null,
-        value: 'Article'
+        value: type
       }
     ]
   }
@@ -63,7 +40,7 @@ describe('ItemSearchResultGridElementComponent', () => {
       declarations: [ItemSearchResultGridElementComponent, TruncatePipe],
       providers: [
         { provide: TruncatableService, useValue: truncatableServiceStub },
-        { provide: 'objectElementProvider', useValue: (mockItemWithoutAuthorAndDate) }
+        { provide: 'objectElementProvider', useValue: (mockItemWithRelationshipType) }
       ],
       schemas: [NO_ERRORS_SCHEMA]
     }).overrideComponent(ItemSearchResultGridElementComponent, {
@@ -76,51 +53,9 @@ describe('ItemSearchResultGridElementComponent', () => {
     itemSearchResultGridElementComponent = fixture.componentInstance;
   }));
 
-  describe('When the item has an author', () => {
-    beforeEach(() => {
-      itemSearchResultGridElementComponent.dso = mockItemWithAuthorAndDate.indexableObject;
-      fixture.detectChanges();
-    });
-
-    it('should show the author paragraph', () => {
-      const itemAuthorField = fixture.debugElement.query(By.css('p.item-authors'));
-      expect(itemAuthorField).not.toBeNull();
-    });
-  });
-
-  describe('When the item has no author', () => {
-    beforeEach(() => {
-      itemSearchResultGridElementComponent.dso = mockItemWithoutAuthorAndDate.indexableObject;
-      fixture.detectChanges();
-    });
-
-    it('should not show the author paragraph', () => {
-      const itemAuthorField = fixture.debugElement.query(By.css('p.item-authors'));
-      expect(itemAuthorField).toBeNull();
-    });
-  });
-
-  describe('When the item has an issuedate', () => {
-    beforeEach(() => {
-      itemSearchResultGridElementComponent.dso = mockItemWithAuthorAndDate.indexableObject;
-      fixture.detectChanges();
-    });
-
-    it('should show the issuedate span', () => {
-      const itemAuthorField = fixture.debugElement.query(By.css('span.item-date'));
-      expect(itemAuthorField).not.toBeNull();
-    });
-  });
-
-  describe('When the item has no issuedate', () => {
-    beforeEach(() => {
-      itemSearchResultGridElementComponent.dso = mockItemWithoutAuthorAndDate.indexableObject;
-      fixture.detectChanges();
-    });
-
-    it('should not show the issuedate span', () => {
-      const dateField = fixture.debugElement.query(By.css('span.item-date'));
-      expect(dateField).toBeNull();
-    });
+  it('should show send the object to item-type-switcher using viewMode "Card"', () => {
+    const itemTypeSwitcherComp = fixture.debugElement.query(By.css('ds-item-type-switcher')).componentInstance;
+    expect(itemTypeSwitcherComp.object).toBe(mockItemWithRelationshipType);
+    expect(itemTypeSwitcherComp.viewMode).toEqual(ItemViewMode.Card);
   });
 });
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts
index 30c36b3af9f38b04cb35be09ff3399ede565fa4f..7bbe41fe60c475b4ba296d434cacc5fe0c7a5a95 100644
--- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts
+++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts
@@ -6,6 +6,7 @@ import { Item } from '../../../../core/shared/item.model';
 import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
 import { SetViewMode } from '../../../view-mode';
 import { focusShadow } from '../../../../shared/animations/focus';
+import { ItemViewMode } from '../../../items/item-type-decorator';
 
 @Component({
   selector: 'ds-item-search-result-grid-element',
@@ -15,4 +16,6 @@ import { focusShadow } from '../../../../shared/animations/focus';
 })
 
 @renderElementsFor(ItemSearchResult, SetViewMode.Grid)
-export class ItemSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> {}
+export class ItemSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> {
+  viewMode = ItemViewMode.Card;
+}
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 0961dc96ee3c1fd39f0dd8e78c3390c5d1f16480..5f31d52ae7add55e9f990b1e53bf7a02cdae2cb7 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
@@ -7,6 +7,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m
 import { TruncatableService } from '../../truncatable/truncatable.service';
 import { Observable } from 'rxjs';
 import { Metadata } from '../../../core/shared/metadata.utils';
+import { hasValue } from '../../empty.util';
 
 @Component({
   selector: 'ds-search-result-grid-element',
@@ -16,9 +17,11 @@ import { Metadata } from '../../../core/shared/metadata.utils';
 export class SearchResultGridElementComponent<T extends SearchResult<K>, K extends DSpaceObject> extends AbstractListableElementComponent<T> {
   dso: K;
 
-  public constructor(@Inject('objectElementProvider') public listableObject: ListableObject, private truncatableService: TruncatableService) {
+  public constructor(@Inject('objectElementProvider') public listableObject: ListableObject, protected truncatableService: TruncatableService) {
     super(listableObject);
-    this.dso = this.object.indexableObject;
+    if (hasValue(this.object)) {
+      this.dso = this.object.indexableObject;
+    }
   }
 
   /**
diff --git a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts
index 9adf255523b2da9d616a3c3d31fec1844569c8ab..082347be0b7e19213fd06721d2319ad7e32a719d 100644
--- a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts
+++ b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts
@@ -24,7 +24,7 @@ const mockSearchResult = {
   hitHighlights: new MetadataMap()
 } as ItemSearchResult;
 
-describe('ItemSearchResultComponent', () => {
+describe('TypedItemSearchResultListElementComponent', () => {
   let comp: TypedItemSearchResultListElementComponent;
   let fixture: ComponentFixture<TypedItemSearchResultListElementComponent>;
 
diff --git a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts
index 7df3ab5681f065cf9d26029ce11fb18a34dca54a..dd1b5a7e5f64c8d3709975d4a4aa19aa07319104 100644
--- a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts
+++ b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts
@@ -11,7 +11,7 @@ import { MetadataMap } from '../../../../core/shared/metadata.models';
  * A generic component for displaying item list elements
  */
 @Component({
-  selector: 'ds-item-search-result',
+  selector: 'ds-item-search-result-list-element',
   template: ''
 })
 export class TypedItemSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {
diff --git a/src/app/shared/object-list/item-type-badge/item-type-badge.component.html b/src/app/shared/object-list/item-type-badge/item-type-badge.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..35d76638018a58a452f9517300abcb11ce08e719
--- /dev/null
+++ b/src/app/shared/object-list/item-type-badge/item-type-badge.component.html
@@ -0,0 +1,3 @@
+<div *ngIf="object && object.indexableObject && object.indexableObject.firstMetadataValue('relationship.type') as type">
+  <span class="badge badge-light">{{ type.toLowerCase() + '.listelement.badge' | translate }}</span>
+</div>
diff --git a/src/app/shared/object-list/item-type-badge/item-type-badge.component.spec.ts b/src/app/shared/object-list/item-type-badge/item-type-badge.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..04c40b73ff18911f22179c24d5e011a1777971ef
--- /dev/null
+++ b/src/app/shared/object-list/item-type-badge/item-type-badge.component.spec.ts
@@ -0,0 +1,83 @@
+import { ItemSearchResult } from '../../object-collection/shared/item-search-result.model';
+import { Item } from '../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { TranslateModule } from '@ngx-translate/core';
+import { TruncatePipe } from '../../utils/truncate.pipe';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { ItemTypeBadgeComponent } from './item-type-badge.component';
+import { By } from '@angular/platform-browser';
+
+let comp: ItemTypeBadgeComponent;
+let fixture: ComponentFixture<ItemTypeBadgeComponent>;
+
+const type = 'authorOfPublication';
+
+const mockItemWithRelationshipType: ItemSearchResult = new ItemSearchResult();
+mockItemWithRelationshipType.hitHighlights = {};
+mockItemWithRelationshipType.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'relationship.type': [
+      {
+        language: 'en_US',
+        value: type
+      }
+    ]
+  }
+});
+
+const mockItemWithoutRelationshipType: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutRelationshipType.hitHighlights = {};
+mockItemWithoutRelationshipType.indexableObject = Object.assign(new Item(), {
+  bitstreams: observableOf({}),
+  metadata: {
+    'dc.title': [
+      {
+        language: 'en_US',
+        value: 'This is just another title'
+      }
+    ]
+  }
+});
+
+describe('ItemTypeBadgeComponent', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [TranslateModule.forRoot()],
+      declarations: [ItemTypeBadgeComponent, TruncatePipe],
+      schemas: [NO_ERRORS_SCHEMA]
+    }).overrideComponent(ItemTypeBadgeComponent, {
+      set: { changeDetection: ChangeDetectionStrategy.Default }
+    }).compileComponents();
+  }));
+
+  beforeEach(async(() => {
+    fixture = TestBed.createComponent(ItemTypeBadgeComponent);
+    comp = fixture.componentInstance;
+  }));
+
+  describe('When the item has a relationship type', () => {
+    beforeEach(() => {
+      comp.object = mockItemWithRelationshipType;
+      fixture.detectChanges();
+    });
+
+    it('should show the relationship type badge', () => {
+      const badge = fixture.debugElement.query(By.css('span.badge'));
+      expect(badge.nativeElement.textContent).toContain(type.toLowerCase());
+    });
+  });
+
+  describe('When the item has no relationship type', () => {
+    beforeEach(() => {
+      comp.object = mockItemWithoutRelationshipType;
+      fixture.detectChanges();
+    });
+
+    it('should not show a badge', () => {
+      const badge = fixture.debugElement.query(By.css('span.badge'));
+      expect(badge).toBeNull();
+    });
+  });
+});
diff --git a/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts b/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9ffba33758561a63dbfdb6d57956a7711a4b7431
--- /dev/null
+++ b/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts
@@ -0,0 +1,12 @@
+import { Component, Input } from '@angular/core';
+import { ListableObject } from '../../object-collection/shared/listable-object.model';
+import { SearchResult } from '../../../+search-page/search-result.model';
+import { DSpaceObject } from '../../../core/shared/dspace-object.model';
+
+@Component({
+  selector: 'ds-item-type-badge',
+  templateUrl: './item-type-badge.component.html'
+})
+export class ItemTypeBadgeComponent {
+  @Input() object: SearchResult<DSpaceObject>;
+}
diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html
index a2617a956f2a316ced6592f8cb2fb45e51c1bf26..051a27bde7cc83af756d8e6c5710038ec820a6cc 100644
--- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html
+++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html
@@ -1,4 +1,2 @@
-<div *ngIf="object && object.indexableObject && object.indexableObject.firstMetadataValue('relationship.type') as type">
-  <span class="badge badge-light">{{ type.toLowerCase() + '.listelement.badge' | translate }}</span>
-</div>
+<ds-item-type-badge [object]="object"></ds-item-type-badge>
 <ds-item-type-switcher [object]="object" [viewMode]="viewMode"></ds-item-type-switcher>
diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts
index a370d3a6329223a5b5573317de4e0f3aae05764c..8f410184042dad4020212b5aa95439e8d1edaa4a 100644
--- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts
+++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts
@@ -33,20 +33,6 @@ mockItemWithRelationshipType.indexableObject = Object.assign(new Item(), {
   }
 });
 
-const mockItemWithoutRelationshipType: ItemSearchResult = new ItemSearchResult();
-mockItemWithoutRelationshipType.hitHighlights = {};
-mockItemWithoutRelationshipType.indexableObject = Object.assign(new Item(), {
-  bitstreams: observableOf({}),
-  metadata: {
-    'dc.title': [
-      {
-        language: 'en_US',
-        value: 'This is just another title'
-      }
-    ]
-  }
-});
-
 describe('ItemSearchResultListElementComponent', () => {
   beforeEach(async(() => {
     TestBed.configureTestingModule({
@@ -54,7 +40,7 @@ describe('ItemSearchResultListElementComponent', () => {
       declarations: [ItemSearchResultListElementComponent, TruncatePipe],
       providers: [
         { provide: TruncatableService, useValue: truncatableServiceStub },
-        { provide: 'objectElementProvider', useValue: (mockItemWithoutRelationshipType) }
+        { provide: 'objectElementProvider', useValue: (mockItemWithRelationshipType) }
       ],
       schemas: [NO_ERRORS_SCHEMA]
     }).overrideComponent(ItemSearchResultListElementComponent, {
@@ -67,27 +53,8 @@ describe('ItemSearchResultListElementComponent', () => {
     itemSearchResultListElementComponent = fixture.componentInstance;
   }));
 
-  describe('When the item has a relationship type', () => {
-    beforeEach(() => {
-      itemSearchResultListElementComponent.object = mockItemWithRelationshipType;
-      fixture.detectChanges();
-    });
-
-    it('should show the relationship type badge', () => {
-      const badge = fixture.debugElement.query(By.css('span.badge'));
-      expect(badge.nativeElement.textContent).toContain(type.toLowerCase());
-    });
-  });
-
-  describe('When the item has no relationship type', () => {
-    beforeEach(() => {
-      itemSearchResultListElementComponent.object = mockItemWithoutRelationshipType;
-      fixture.detectChanges();
-    });
-
-    it('should not show a badge', () => {
-      const badge = fixture.debugElement.query(By.css('span.badge'));
-      expect(badge).toBeNull();
-    });
+  it('should show a badge on top of the list element', () => {
+    const badge = fixture.debugElement.query(By.css('ds-item-type-badge')).componentInstance;
+    expect(badge.object).toBe(mockItemWithRelationshipType);
   });
 });
diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html
index 22a58dd7fc94e645fe1f4aba4c4b37fd02bf7fca..c16a1530263521e81f0cc4624cd5dc9347c2872e 100644
--- a/src/app/shared/pagination/pagination.component.html
+++ b/src/app/shared/pagination/pagination.component.html
@@ -1,9 +1,9 @@
 <div *ngIf="currentPageState == undefined || currentPageState == currentPage">
   <div  class="pagination-masked clearfix top">
     <div class="row">
-      <div *ngIf="!hidePaginationDetail" class="col-auto pagination-info">
-        <span class="align-middle hidden-xs-down">{{ 'pagination.showing.label' | translate }}</span>
-        <span class="align-middle" *ngIf="collectionSize">{{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}}</span>
+      <div *ngIf="!hidePaginationDetail && collectionSize > 0" class="col-auto pagination-info">
+          <span class="align-middle hidden-xs-down">{{ 'pagination.showing.label' | translate }}</span>
+          <span class="align-middle">{{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}}</span>
       </div>
       <div class="col">
         <div *ngIf="!hideGear" ngbDropdown #paginationControls="ngbDropdown" placement="bottom-right" class="d-inline-block float-right">
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index 816139c8b96c082fa0adf211c4f5fafe65f3a675..66afb1e41bc333e2d33fb38366ee668494f9a2b2 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -138,6 +138,9 @@ import { RoleDirective } from './roles/role.directive';
 import { UserMenuComponent } from './auth-nav-menu/user-menu/user-menu.component';
 import { ClaimedTaskActionsReturnToPoolComponent } from './mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component';
 import { ItemDetailPreviewFieldComponent } from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component';
+import { TypedItemSearchResultGridElementComponent } from './object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
+import { PublicationGridElementComponent } from './object-grid/item-grid-element/item-types/publication/publication-grid-element.component';
+import { ItemTypeBadgeComponent } from './object-list/item-type-badge/item-type-badge.component';
 
 const MODULES = [
   // Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -256,8 +259,10 @@ const COMPONENTS = [
   CollectionSearchResultListElementComponent,
   ItemSearchResultListElementComponent,
   TypedItemSearchResultListElementComponent,
+  TypedItemSearchResultGridElementComponent,
   ItemTypeSwitcherComponent,
-  BrowseByComponent
+  BrowseByComponent,
+  ItemTypeBadgeComponent
 ];
 
 const ENTRY_COMPONENTS = [
@@ -275,6 +280,7 @@ const ENTRY_COMPONENTS = [
   CommunityGridElementComponent,
   SearchResultGridElementComponent,
   PublicationListElementComponent,
+  PublicationGridElementComponent,
   BrowseEntryListElementComponent,
   MyDSpaceResultDetailElementComponent,
   SearchResultGridElementComponent,
diff --git a/themes/mantis/app/+item-page/simple/item-types/journal-issue/journal-issue.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
similarity index 100%
rename from themes/mantis/app/+item-page/simple/item-types/journal-issue/journal-issue.component.html
rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
diff --git a/themes/mantis/app/+item-page/simple/item-types/journal-issue/journal-issue.component.scss b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss
similarity index 85%
rename from themes/mantis/app/+item-page/simple/item-types/journal-issue/journal-issue.component.scss
rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss
index 7ce24acc15823eb8ba3f168b5171e641f8bdb175..3caa55f533547857b1eb26990ea9fd2861b2ed53 100644
--- a/themes/mantis/app/+item-page/simple/item-types/journal-issue/journal-issue.component.scss
+++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss
@@ -1,4 +1,4 @@
-@import 'src/app/+item-page/simple/item-types/journal-issue/journal-issue.component.scss';
+@import 'src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss';
 
 :host {
     > * {
diff --git a/themes/mantis/app/+item-page/simple/item-types/journal-volume/journal-volume.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
similarity index 100%
rename from themes/mantis/app/+item-page/simple/item-types/journal-volume/journal-volume.component.html
rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
diff --git a/themes/mantis/app/+item-page/simple/item-types/journal-volume/journal-volume.component.scss b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss
similarity index 85%
rename from themes/mantis/app/+item-page/simple/item-types/journal-volume/journal-volume.component.scss
rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss
index ab1bc700b1907613c7706126fe1aaf3cf32730d3..5c2534b3182ab5968631cf6efd4271c33c9b1bc2 100644
--- a/themes/mantis/app/+item-page/simple/item-types/journal-volume/journal-volume.component.scss
+++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss
@@ -1,4 +1,4 @@
-@import 'src/app/+item-page/simple/item-types/journal-volume/journal-volume.component.scss';
+@import 'src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss';
 
 :host {
     > * {
diff --git a/themes/mantis/app/+item-page/simple/item-types/journal/journal.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
similarity index 100%
rename from themes/mantis/app/+item-page/simple/item-types/journal/journal.component.html
rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
diff --git a/themes/mantis/app/+item-page/simple/item-types/journal/journal.component.scss b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss
similarity index 89%
rename from themes/mantis/app/+item-page/simple/item-types/journal/journal.component.scss
rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss
index 6d97cbf5c35168c9143fdffae59b5a722d642f83..5c0d1c44b829110e776c37447990cc568fb45103 100644
--- a/themes/mantis/app/+item-page/simple/item-types/journal/journal.component.scss
+++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss
@@ -1,4 +1,4 @@
-@import 'src/app/+item-page/simple/item-types/journal/journal.component.scss';
+@import 'src/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss';
 
 :host {
     > * {
diff --git a/themes/mantis/app/+item-page/simple/item-types/orgunit/orgunit.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html
similarity index 100%
rename from themes/mantis/app/+item-page/simple/item-types/orgunit/orgunit.component.html
rename to themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html
diff --git a/themes/mantis/app/+item-page/simple/item-types/orgunit/orgunit.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss
similarity index 86%
rename from themes/mantis/app/+item-page/simple/item-types/orgunit/orgunit.component.scss
rename to themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss
index 5b2bdb0382717852ad7e355bd89cabb2f1ecf19f..54651aede0f5c7487ed59528d20b1a3d1fb84af5 100644
--- a/themes/mantis/app/+item-page/simple/item-types/orgunit/orgunit.component.scss
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss
@@ -1,4 +1,4 @@
-@import 'src/app/+item-page/simple/item-types/orgunit/orgunit.component.scss';
+@import 'src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss';
 
 :host {
     > * {
diff --git a/themes/mantis/app/+item-page/simple/item-types/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
similarity index 100%
rename from themes/mantis/app/+item-page/simple/item-types/person/person.component.html
rename to themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
diff --git a/themes/mantis/app/+item-page/simple/item-types/person/person.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss
similarity index 89%
rename from themes/mantis/app/+item-page/simple/item-types/person/person.component.scss
rename to themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss
index 3b454aab0e81ecda662544377df6534089cf94f2..48571b05b23ff32e31e076a0c1b95bed0e328098 100644
--- a/themes/mantis/app/+item-page/simple/item-types/person/person.component.scss
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss
@@ -1,4 +1,4 @@
-@import 'src/app/+item-page/simple/item-types/person/person.component.scss';
+@import 'src/app/entity-groups/research-entities/item-pages/person/person.component.scss';
 
 :host {
     > * {
diff --git a/themes/mantis/app/+item-page/simple/item-types/project/project.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html
similarity index 100%
rename from themes/mantis/app/+item-page/simple/item-types/project/project.component.html
rename to themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html
diff --git a/themes/mantis/app/+item-page/simple/item-types/project/project.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss
similarity index 86%
rename from themes/mantis/app/+item-page/simple/item-types/project/project.component.scss
rename to themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss
index 9c9aa9c629931071328255060cdd0926e1a368b7..d2707d30ccb0455daba9bacadcfe1058216d93d3 100644
--- a/themes/mantis/app/+item-page/simple/item-types/project/project.component.scss
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss
@@ -1,4 +1,4 @@
-@import 'src/app/+item-page/simple/item-types/project/project.component.scss';
+@import 'src/app/entity-groups/research-entities/item-pages/project/project.component.scss';
 
 :host {
     > * {