diff --git a/config/environment.default.js b/config/environment.default.js
index 24386d6cf71f8e14e799e0c938b88bc60c2977f4..df4f89a2fe1766d86f20a4620c3614724721f6b4 100644
--- a/config/environment.default.js
+++ b/config/environment.default.js
@@ -9,11 +9,10 @@ module.exports = {
   },
   // The REST API server settings.
   rest: {
-    ssl: true,
-    host: 'dspace7.4science.cloud',
-    port: 443,
-    // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
-    nameSpace: '/server/api'
+      ssl: true,
+      host: 'dspace7-entities.atmire.com',
+      port: 443,
+      nameSpace: '/server/api'
   },
   // Caching settings
   cache: {
diff --git a/package.json b/package.json
index a664c8daa43dbafab4c9cfb78193a542a257792a..dbdc10a8cab6eb82879ca12c29eaf3ad9294dcab 100644
--- a/package.json
+++ b/package.json
@@ -230,7 +230,7 @@
     "rollup-plugin-node-globals": "1.2.1",
     "rollup-plugin-node-resolve": "^3.0.3",
     "rollup-plugin-terser": "^2.0.2",
-    "sass-loader": "7.1.0",
+    "sass-loader": "^7.1.0",
     "script-ext-html-webpack-plugin": "2.0.1",
     "source-map": "0.7.3",
     "source-map-loader": "0.2.4",
diff --git a/resources/fonts/README.md b/resources/fonts/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e4817b8572c8b6f57625d13de27da22a641659f3
--- /dev/null
+++ b/resources/fonts/README.md
@@ -0,0 +1,3 @@
+# Supported font formats
+
+DSpace supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts. 
diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5
index ff5ca4f93ea7502329e8cf400251766a500888ca..299c2afe634626e79f0f017cec35d230b8e2ac74 100644
--- a/resources/i18n/en.json5
+++ b/resources/i18n/en.json5
@@ -340,6 +340,14 @@
 
 
 
+  "communityList.tabTitle": "DSpace - Community List",
+
+  "communityList.title": "List of Communities",
+
+  "communityList.showMore": "Show More",
+
+
+
   "community.create.head": "Create a Community",
 
   "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}",
@@ -821,9 +829,17 @@
 
   "item.page.person.search.title": "Articles by this author",
 
-  "item.page.related-items.view-more": "View more",
+  "item.page.related-items.view-more": "Show {{ amount }} more",
+
+  "item.page.related-items.view-less": "Hide last {{ amount }}",
+
+  "item.page.relationships.isAuthorOfPublication": "Publications",
 
-  "item.page.related-items.view-less": "View less",
+  "item.page.relationships.isJournalOfPublication": "Publications",
+
+  "item.page.relationships.isOrgUnitOfPerson": "Authors",
+
+  "item.page.relationships.isOrgUnitOfProject": "Research Projects",
 
   "item.page.subject": "Keywords",
 
@@ -1275,6 +1291,8 @@
 
   "project.page.titleprefix": "Research Project: ",
 
+  "project.search.results.head": "Project Search Results",
+
 
 
   "publication.listelement.badge": "Publication",
@@ -1547,6 +1565,10 @@
 
   "submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Volume": "Search for Journal Volumes",
 
+  "submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding Agency": "Search for Funding Agencies",
+
+  "submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding": "Search for Funding",
+
   "submission.sections.describe.relationship-lookup.selection-tab.tab-title": "Current Selection ({{ count }})",
 
   "submission.sections.describe.relationship-lookup.title.Journal Issue": "Journal Issues",
@@ -1557,6 +1579,10 @@
 
   "submission.sections.describe.relationship-lookup.title.Author": "Authors",
 
+  "submission.sections.describe.relationship-lookup.title.Funding Agency": "Funding Agency",
+
+  "submission.sections.describe.relationship-lookup.title.Funding": "Funding",
+
   "submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown",
 
   "submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings",
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 cb7aa1ef91d7e88a39177f3bf3e1df0f4d14d518..ec4003c108fb11f59a997e48cbd1f80cdf029c5b 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
@@ -5,7 +5,7 @@ import { PaginatedList } from '../../../core/data/paginated-list';
 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 { FindListOptions } 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';
@@ -35,7 +35,7 @@ export class BitstreamFormatsComponent implements OnInit {
    * The current pagination configuration for the page used by the FindAll method
    * Currently simply renders all bitstream formats
    */
-  config: FindAllOptions = Object.assign(new FindAllOptions(), {
+  config: FindListOptions = Object.assign(new FindListOptions(), {
     elementsPerPage: 20
   });
 
@@ -145,7 +145,7 @@ export class BitstreamFormatsComponent implements OnInit {
    * @param event The page change event
    */
   onPageChange(event) {
-    this.config = Object.assign(new FindAllOptions(), this.config, {
+    this.config = Object.assign(new FindListOptions(), this.config, {
       currentPage: event,
     });
     this.pageConfig.currentPage = event;
diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts
index 28460f567a4650b940793af60e94170043a261b3..5c54becddee4bf51b8998faa0b855ae9961f7dae 100644
--- a/src/app/+item-page/item-page.module.ts
+++ b/src/app/+item-page/item-page.module.ts
@@ -26,7 +26,9 @@ import { MetadataRepresentationListComponent } from './simple/metadata-represent
 import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
 import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
 import { MetadataFieldWrapperComponent } from './field-components/metadata-field-wrapper/metadata-field-wrapper.component';
+import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
 import { StatisticsModule } from '../statistics/statistics.module';
+import { AbstractIncrementalListComponent } from './simple/abstract-incremental-list/abstract-incremental-list.component';
 
 @NgModule({
   imports: [
@@ -55,7 +57,9 @@ import { StatisticsModule } from '../statistics/statistics.module';
     ItemComponent,
     GenericItemPageFieldComponent,
     MetadataRepresentationListComponent,
-    RelatedEntitiesSearchComponent
+    RelatedEntitiesSearchComponent,
+    TabbedRelatedEntitiesSearchComponent,
+    AbstractIncrementalListComponent
   ],
   exports: [
     ItemComponent,
@@ -65,7 +69,8 @@ import { StatisticsModule } from '../statistics/statistics.module';
     RelatedEntitiesSearchComponent,
     RelatedItemsComponent,
     MetadataRepresentationListComponent,
-    ItemPageTitleFieldComponent
+    ItemPageTitleFieldComponent,
+    TabbedRelatedEntitiesSearchComponent
   ],
   entryComponents: [
     PublicationComponent
diff --git a/src/app/+item-page/simple/abstract-incremental-list/abstract-incremental-list.component.ts b/src/app/+item-page/simple/abstract-incremental-list/abstract-incremental-list.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e2c0823bf803fa334ccd46baf7b1e787da44fd9b
--- /dev/null
+++ b/src/app/+item-page/simple/abstract-incremental-list/abstract-incremental-list.component.ts
@@ -0,0 +1,73 @@
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Subscription } from 'rxjs/internal/Subscription';
+import { hasValue, isNotEmpty } from '../../../shared/empty.util';
+
+@Component({
+  selector: 'ds-abstract-incremental-list',
+  template: ``,
+})
+/**
+ * An abstract component for displaying an incremental list of objects
+ */
+export class AbstractIncrementalListComponent<T> implements OnInit, OnDestroy {
+  /**
+   * The amount to increment the list by
+   * Define this amount in the child component overriding this component
+   */
+  incrementBy: number;
+
+  /**
+   * All pages of objects to display as an array
+   */
+  objects: T[];
+
+  /**
+   * A list of open subscriptions
+   */
+  subscriptions: Subscription[];
+
+  ngOnInit(): void {
+    this.objects = [];
+    this.subscriptions = [];
+    this.increase();
+  }
+
+  /**
+   * Get a specific page
+   * > Override this method to return a specific page
+   * @param page  The page to fetch
+   */
+  getPage(page: number): T {
+    return undefined;
+  }
+
+  /**
+   * Increase the amount displayed
+   */
+  increase() {
+    const page = this.getPage(this.objects.length + 1);
+    if (hasValue(page)) {
+      this.objects.push(page);
+    }
+  }
+
+  /**
+   * Decrease the amount displayed
+   */
+  decrease() {
+    if (this.objects.length > 1) {
+      this.objects.pop();
+    }
+  }
+
+  /**
+   * Unsubscribe from any open subscriptions
+   */
+  ngOnDestroy(): void {
+    if (isNotEmpty(this.subscriptions)) {
+      this.subscriptions.forEach((sub: Subscription) => {
+        sub.unsubscribe();
+      });
+    }
+  }
+}
diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html
index 750029b58b81f5f17daaf3f54770afec882b6f24..d1281f450a3d879efcaa43a0239535aaf91ae5f8 100644
--- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html
+++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html
@@ -1,11 +1,20 @@
-<ds-metadata-field-wrapper *ngIf="representations$ && (representations$ | async)?.length > 0" [label]="label">
-    <ds-metadata-representation-loader *ngFor="let rep of (representations$ | async)"
-                                       [mdRepresentation]="rep">
-    </ds-metadata-representation-loader>
-  <div *ngIf="(representations$ | async)?.length < total" class="mt-2">
-    <a [routerLink]="" (click)="viewMore()">{{'item.page.related-items.view-more' | translate}}</a>
-  </div>
-  <div *ngIf="limit > originalLimit" class="mt-2">
-    <a [routerLink]="" (click)="viewLess()">{{'item.page.related-items.view-less' | translate}}</a>
-  </div>
+<ds-metadata-field-wrapper [label]="label">
+  <ng-container *ngFor="let objectPage of objects; let i = index">
+    <ng-container *ngVar="(objectPage | async) as representations">
+      <ds-metadata-representation-loader *ngFor="let rep of representations"
+                                         [mdRepresentation]="rep">
+      </ds-metadata-representation-loader>
+      <ds-loading *ngIf="(i + 1) === objects.length && (i > 0) && (!representations || representations?.length === 0)" message="{{'loading.default' | translate}}"></ds-loading>
+      <div class="d-inline-block w-100 mt-2" *ngIf="(i + 1) === objects.length && representations?.length > 0">
+        <div *ngIf="(objects.length * incrementBy) < total" class="float-left">
+          <a [routerLink]="" (click)="increase()">{{'item.page.related-items.view-more' |
+            translate:{ amount: (total - (objects.length * incrementBy) < incrementBy) ? total - (objects.length * incrementBy) : incrementBy } }}</a>
+        </div>
+        <div *ngIf="objects.length > 1" class="float-right">
+          <a [routerLink]="" (click)="decrease()">{{'item.page.related-items.view-less' |
+            translate:{ amount: representations?.length } }}</a>
+        </div>
+      </div>
+    </ng-container>
+  </ng-container>
 </ds-metadata-field-wrapper>
diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts
index 7beabdceba231482714e3e1566035fdefffba8b9..ad62ce44180be4aedaf63b6ee34ee343e786ff2e 100644
--- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts
+++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts
@@ -7,6 +7,8 @@ import { Item } from '../../../core/shared/item.model';
 import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
 import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
 import { TranslateModule } from '@ngx-translate/core';
+import { VarDirective } from '../../../shared/utils/var.directive';
+import { of as observableOf } from 'rxjs';
 
 const itemType = 'Person';
 const metadataField = 'dc.contributor.author';
@@ -64,7 +66,7 @@ describe('MetadataRepresentationListComponent', () => {
   beforeEach(async(() => {
     TestBed.configureTestingModule({
       imports: [TranslateModule.forRoot()],
-      declarations: [MetadataRepresentationListComponent],
+      declarations: [MetadataRepresentationListComponent, VarDirective],
       providers: [
         { provide: RelationshipService, useValue: relationshipService }
       ],
@@ -88,33 +90,29 @@ describe('MetadataRepresentationListComponent', () => {
     expect(fields.length).toBe(2);
   });
 
-  it('should initialize the original limit', () => {
-    expect(comp.originalLimit).toEqual(comp.limit);
+  it('should contain one page of items', () => {
+    expect(comp.objects.length).toEqual(1);
   });
 
-  describe('when viewMore is called', () => {
+  describe('when increase is called', () => {
     beforeEach(() => {
-      comp.viewMore();
+      comp.increase();
     });
 
-    it('should set the limit to a high number in order to retrieve all metadata representations', () => {
-      expect(comp.limit).toBeGreaterThanOrEqual(999);
+    it('should add a new page to the list', () => {
+      expect(comp.objects.length).toEqual(2);
     });
   });
 
-  describe('when viewLess is called', () => {
-    let originalLimit;
-
+  describe('when decrease is called', () => {
     beforeEach(() => {
-      // Store the original value of limit
-      originalLimit = comp.limit;
-      // Set limit to a random number
-      comp.limit = 458;
-      comp.viewLess();
+      // Add a second page
+      comp.objects.push(observableOf(undefined));
+      comp.decrease();
     });
 
-    it('should reset the limit to the original value', () => {
-      expect(comp.limit).toEqual(originalLimit);
+    it('should decrease the list of pages', () => {
+      expect(comp.objects.length).toEqual(1);
     });
   });
 
diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts
index 1fa623f6c9088c3af8081a133ae5522b7d28db98..23484f22e06a73d5e7f45da07ea85b0d3eb04406 100644
--- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts
+++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts
@@ -1,16 +1,16 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, Input } from '@angular/core';
 import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
 import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs';
 import { RelationshipService } from '../../../core/data/relationship.service';
 import { MetadataValue } from '../../../core/shared/metadata.models';
 import { getSucceededRemoteData } from '../../../core/shared/operators';
-import { switchMap } from 'rxjs/operators';
+import { filter, map, switchMap } from 'rxjs/operators';
 import { RemoteData } from '../../../core/data/remote-data';
 import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
 import { Item } from '../../../core/shared/item.model';
 import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
 import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
-import { map, filter } from 'rxjs/operators';
+import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
 
 @Component({
   selector: 'ds-metadata-representation-list',
@@ -22,7 +22,7 @@ import { map, filter } from 'rxjs/operators';
  * It expects an itemType to resolve the metadata to a an item
  * It expects a label to put on top of the list
  */
-export class MetadataRepresentationListComponent implements OnInit {
+export class MetadataRepresentationListComponent extends AbstractIncrementalListComponent<Observable<MetadataRepresentation[]>> {
   /**
    * The parent of the list of related items to display
    */
@@ -44,22 +44,11 @@ export class MetadataRepresentationListComponent implements OnInit {
   @Input() label: string;
 
   /**
-   * The max amount of representations to display
+   * The amount to increment the list by when clicking "view more"
    * Defaults to 10
    * The default can optionally be overridden by providing the limit as input to the component
    */
-  @Input() limit = 10;
-
-  /**
-   * A list of metadata-representations to display
-   */
-  representations$: Observable<MetadataRepresentation[]>;
-
-  /**
-   * The originally provided limit
-   * Used for resetting the limit to the original value when collapsing the list
-   */
-  originalLimit: number;
+  @Input() incrementBy = 10;
 
   /**
    * The total amount of metadata values available
@@ -67,30 +56,28 @@ export class MetadataRepresentationListComponent implements OnInit {
   total: number;
 
   constructor(public relationshipService: RelationshipService) {
-  }
-
-  ngOnInit(): void {
-    this.originalLimit = this.limit;
-    this.setRepresentations();
+    super();
   }
 
   /**
-   * Initialize the metadata representations
+   * Get a specific page
+   * @param page  The page to fetch
    */
-  setRepresentations() {
+  getPage(page: number): Observable<MetadataRepresentation[]> {
     const metadata = this.parentItem.findMetadataSortedByPlace(this.metadataField);
     this.total = metadata.length;
-    this.representations$ = this.resolveMetadataRepresentations(metadata);
+    return this.resolveMetadataRepresentations(metadata, page);
   }
 
   /**
    * Resolve a list of metadata values to a list of metadata representations
-   * @param metadata
+   * @param metadata  The list of all metadata values
+   * @param page      The page to return representations for
    */
-  resolveMetadataRepresentations(metadata: MetadataValue[]): Observable<MetadataRepresentation[]> {
+  resolveMetadataRepresentations(metadata: MetadataValue[], page: number): Observable<MetadataRepresentation[]> {
     return observableZip(
       ...metadata
-        .slice(0, this.limit)
+        .slice((this.objects.length * this.incrementBy), (this.objects.length * this.incrementBy) + this.incrementBy)
         .map((metadatum: any) => Object.assign(new MetadataValue(), metadatum))
         .map((metadatum: MetadataValue) => {
           if (metadatum.isVirtual) {
@@ -115,20 +102,4 @@ export class MetadataRepresentationListComponent implements OnInit {
         })
     );
   }
-
-  /**
-   * Expand the list to display all metadata representations
-   */
-  viewMore() {
-    this.limit = 9999;
-    this.setRepresentations();
-  }
-
-  /**
-   * Collapse the list to display the originally displayed metadata representations
-   */
-  viewLess() {
-    this.limit = this.originalLimit;
-    this.setRepresentations();
-  }
 }
diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html
index ebf3a0fd6e54658a8a09e147efe8fbe1c2c5d3f0..75f3b7aaad3de0b4bb4b951acf04d4cf619d83ff 100644
--- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html
+++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html
@@ -1,6 +1,7 @@
-<ds-filtered-search-page
+<ds-configuration-search-page
   [fixedFilterQuery]="fixedFilter"
+  [configuration]="configuration"
   [configuration$]="configuration$"
   [searchEnabled]="searchEnabled"
   [sideBarWidth]="sideBarWidth">
-</ds-filtered-search-page>
\ No newline at end of file
+</ds-configuration-search-page>
diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts
index 65385b0442fddd50da4aa1e218dbcd408e3964fb..d9e5dd9dce3c0a408b32dfc221434a64a9e55294 100644
--- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts
+++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts
@@ -14,7 +14,7 @@ describe('RelatedEntitiesSearchComponent', () => {
     id: 'id1'
   });
   const mockRelationType = 'publicationsOfAuthor';
-  const mockRelationEntityType = 'publication';
+  const mockConfiguration = 'publication';
   const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
 
   beforeEach(async(() => {
@@ -30,7 +30,7 @@ describe('RelatedEntitiesSearchComponent', () => {
     comp = fixture.componentInstance;
     comp.relationType = mockRelationType;
     comp.item = mockItem;
-    comp.relationEntityType = mockRelationEntityType;
+    comp.configuration = mockConfiguration;
     fixture.detectChanges();
   });
 
@@ -40,7 +40,7 @@ describe('RelatedEntitiesSearchComponent', () => {
 
   it('should create a configuration$', () => {
     comp.configuration$.subscribe((configuration) => {
-      expect(configuration).toEqual(mockRelationEntityType);
+      expect(configuration).toEqual(mockConfiguration);
     })
   });
 
diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts
index d20bee2d4aa059391dd992ef440773688fc0fa50..595734ed9fdfffaaf841c79c58c6987f287f6674 100644
--- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts
+++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts
@@ -23,16 +23,14 @@ export class RelatedEntitiesSearchComponent implements OnInit {
   @Input() relationType: string;
 
   /**
-   * The item to render relationships for
+   * An optional configuration to use for the search options
    */
-  @Input() item: Item;
+  @Input() configuration: string;
 
   /**
-   * The entity type of the relationship items to be displayed
-   * e.g. 'publication'
-   * This determines the title of the search results (if search is enabled)
+   * The item to render relationships for
    */
-  @Input() relationEntityType: string;
+  @Input() item: Item;
 
   /**
    * Whether or not the search bar and title should be displayed (defaults to true)
@@ -53,8 +51,8 @@ export class RelatedEntitiesSearchComponent implements OnInit {
     if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) {
       this.fixedFilter = getFilterByRelation(this.relationType, this.item.id);
     }
-    if (isNotEmpty(this.relationEntityType)) {
-      this.configuration$ = of(this.relationEntityType);
+    if (isNotEmpty(this.configuration)) {
+      this.configuration$ = of(this.configuration);
     }
   }
 
diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..f9642d2c01caf97a357a8ac22ba5d707bf6fdf51
--- /dev/null
+++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html
@@ -0,0 +1,22 @@
+<ngb-tabset *ngIf="relationTypes.length > 1" [destroyOnHide]="true" #tabs="ngbTabset" [activeId]="activeTab$ | async" (tabChange)="onTabChange($event)">
+  <ngb-tab *ngFor="let relationType of relationTypes" title="{{'item.page.relationships.' + relationType.label | translate}}" [id]="relationType.filter">
+    <ng-template ngbTabContent>
+      <div class="mt-4">
+        <ds-related-entities-search [item]="item"
+                                    [relationType]="relationType.filter"
+                                    [configuration]="relationType.configuration"
+                                    [searchEnabled]="searchEnabled"
+                                    [sideBarWidth]="sideBarWidth">
+        </ds-related-entities-search>
+      </div>
+    </ng-template>
+  </ngb-tab>
+</ngb-tabset>
+<div *ngIf="relationTypes.length === 1" class="mt-4">
+  <ds-related-entities-search *ngVar="relationTypes[0] as relationType" [item]="item"
+                              [relationType]="relationType.filter"
+                              [configuration]="relationType.configuration"
+                              [searchEnabled]="searchEnabled"
+                              [sideBarWidth]="sideBarWidth">
+  </ds-related-entities-search>
+</div>
diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2d2e682196d0ef2ee12c44f43bf510d1bf622f3b
--- /dev/null
+++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts
@@ -0,0 +1,82 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { Item } from '../../../../core/shared/item.model';
+import { TranslateModule } from '@ngx-translate/core';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { TabbedRelatedEntitiesSearchComponent } from './tabbed-related-entities-search.component';
+import { ActivatedRoute, Router } from '@angular/router';
+import { MockRouter } from '../../../../shared/mocks/mock-router';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { VarDirective } from '../../../../shared/utils/var.directive';
+import { of as observableOf } from 'rxjs';
+
+describe('TabbedRelatedEntitiesSearchComponent', () => {
+  let comp: TabbedRelatedEntitiesSearchComponent;
+  let fixture: ComponentFixture<TabbedRelatedEntitiesSearchComponent>;
+
+  const mockItem = Object.assign(new Item(), {
+    id: 'id1'
+  });
+  const mockRelationType = 'publications';
+  const relationTypes = [
+    {
+      label: mockRelationType,
+      filter: mockRelationType
+    }
+  ];
+
+  const router = new MockRouter();
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [TranslateModule.forRoot(), NoopAnimationsModule, NgbModule.forRoot()],
+      declarations: [TabbedRelatedEntitiesSearchComponent, VarDirective],
+      providers: [
+        {
+          provide: ActivatedRoute,
+          useValue: {
+            queryParams: observableOf({ tab: mockRelationType })
+          },
+        },
+        { provide: Router, useValue: router }
+      ],
+      schemas: [NO_ERRORS_SCHEMA]
+    }).compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TabbedRelatedEntitiesSearchComponent);
+    comp = fixture.componentInstance;
+    comp.item = mockItem;
+    comp.relationTypes = relationTypes;
+    fixture.detectChanges();
+  });
+
+  it('should initialize the activeTab depending on the current query parameters', () => {
+    comp.activeTab$.subscribe((activeTab) => {
+      expect(activeTab).toEqual(mockRelationType);
+    });
+  });
+
+  describe('onTabChange', () => {
+    const event = {
+      currentId: mockRelationType,
+      nextId: 'nextTab'
+    };
+
+    beforeEach(() => {
+      comp.onTabChange(event);
+    });
+
+    it('should call router natigate with the correct arguments', () => {
+      expect(router.navigate).toHaveBeenCalledWith([], {
+        relativeTo: (comp as any).route,
+        queryParams: {
+          tab: event.nextId
+        },
+        queryParamsHandling: 'merge'
+      });
+    });
+  });
+
+});
diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b01eb707201241ab324f0333adc53bac88568e99
--- /dev/null
+++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts
@@ -0,0 +1,76 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { Item } from '../../../../core/shared/item.model';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Observable } from 'rxjs/internal/Observable';
+import { map } from 'rxjs/operators';
+
+@Component({
+  selector: 'ds-tabbed-related-entities-search',
+  templateUrl: './tabbed-related-entities-search.component.html'
+})
+/**
+ * A component to show related items as search results, split into tabs by relationship-type
+ * Related items can be facetted, or queried using an
+ * optional search box.
+ */
+export class TabbedRelatedEntitiesSearchComponent implements OnInit {
+  /**
+   * The types of relationships to fetch items for
+   * e.g. 'isAuthorOfPublication'
+   */
+  @Input() relationTypes: Array<{
+    label: string,
+    filter: string,
+    configuration?: string
+  }>;
+
+  /**
+   * The item to render relationships for
+   */
+  @Input() item: Item;
+
+  /**
+   * Whether or not the search bar and title should be displayed (defaults to true)
+   * @type {boolean}
+   */
+  @Input() searchEnabled = true;
+
+  /**
+   * The ratio of the sidebar's width compared to the search results (1-12) (defaults to 4)
+   * @type {number}
+   */
+  @Input() sideBarWidth = 4;
+
+  /**
+   * The active tab
+   */
+  activeTab$: Observable<string>;
+
+  constructor(private route: ActivatedRoute,
+              private router: Router) {
+  }
+
+  /**
+   * If the url contains a "tab" query parameter, set this tab to be the active tab
+   */
+  ngOnInit(): void {
+    this.activeTab$ = this.route.queryParams.pipe(
+      map((params) => params.tab)
+    );
+  }
+
+  /**
+   * Add a "tab" query parameter to the URL when changing tabs
+   * @param event
+   */
+  onTabChange(event) {
+    this.router.navigate([], {
+      relativeTo: this.route,
+      queryParams: {
+        tab: event.nextId
+      },
+      queryParamsHandling: 'merge'
+    });
+  }
+
+}
diff --git a/src/app/+item-page/simple/related-items/related-items-component.ts b/src/app/+item-page/simple/related-items/related-items-component.ts
index ce502468e9fd311e14a794e294e42d50764d4a3d..0446e53be57ab668895dcbe069837c30ac70d135 100644
--- a/src/app/+item-page/simple/related-items/related-items-component.ts
+++ b/src/app/+item-page/simple/related-items/related-items-component.ts
@@ -1,12 +1,12 @@
-import { Component, HostBinding, Input, OnDestroy, OnInit } from '@angular/core';
+import { Component, Input } from '@angular/core';
 import { Item } from '../../../core/shared/item.model';
 import { Observable } from 'rxjs/internal/Observable';
 import { RemoteData } from '../../../core/data/remote-data';
 import { PaginatedList } from '../../../core/data/paginated-list';
-import { FindAllOptions } from '../../../core/data/request.models';
+import { FindListOptions } from '../../../core/data/request.models';
 import { ViewMode } from '../../../core/shared/view-mode.model';
 import { RelationshipService } from '../../../core/data/relationship.service';
-import { Subscription } from 'rxjs';
+import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
 
 @Component({
   selector: 'ds-related-items',
@@ -17,7 +17,7 @@ import { Subscription } from 'rxjs';
  * This component is used for displaying relations between items
  * It expects a parent item and relationship type, as well as a label to display on top
  */
-export class RelatedItemsComponent implements OnInit, OnDestroy {
+export class RelatedItemsComponent extends AbstractIncrementalListComponent<Observable<RemoteData<PaginatedList<Item>>>> {
   /**
    * The parent of the list of related items to display
    */
@@ -30,30 +30,22 @@ export class RelatedItemsComponent implements OnInit, OnDestroy {
   @Input() relationType: string;
 
   /**
-   * Default options to start a search request with
-   * Optional input, should you wish a different page size (or other options)
-   */
-  @Input() options = Object.assign(new FindAllOptions(), { elementsPerPage: 5 });
-
-  /**
-   * An i18n label to use as a title for the list (usually describes the relation)
-   */
-  @Input() label: string;
-
-  /**
-   * Completely hide the component until there's at least one item visible
+   * The amount to increment the list by when clicking "view more"
+   * Defaults to 5
+   * The default can optionally be overridden by providing the limit as input to the component
    */
-  @HostBinding('class.d-none') hidden = true;
+  @Input() incrementBy = 5;
 
   /**
-   * The list of related items
+   * Default options to start a search request with
+   * Optional input
    */
-  items$: Observable<RemoteData<PaginatedList<Item>>>;
+  @Input() options = new FindListOptions();
 
   /**
-   * Search options for displaying all elements in a list
+   * An i18n label to use as a title for the list (usually describes the relation)
    */
-  allOptions = Object.assign(new FindAllOptions(), { elementsPerPage: 9999 });
+  @Input() label: string;
 
   /**
    * The view-mode we're currently on
@@ -61,48 +53,15 @@ export class RelatedItemsComponent implements OnInit, OnDestroy {
    */
   viewMode = ViewMode.ListElement;
 
-  /**
-   * Whether or not the list is currently expanded to show all related items
-   */
-  showingAll = false;
-
-  /**
-   * Subscription on items used to update the "hidden" property of this component
-   */
-  itemSub: Subscription;
-
   constructor(public relationshipService: RelationshipService) {
-  }
-
-  ngOnInit(): void {
-    this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.options);
-    this.itemSub = this.items$.subscribe((itemsRD: RemoteData<PaginatedList<Item>>) => {
-      this.hidden = !(itemsRD.hasSucceeded && itemsRD.payload && itemsRD.payload.page.length > 0);
-    });
-  }
-
-  /**
-   * Expand the list to display all related items
-   */
-  viewMore() {
-    this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.allOptions);
-    this.showingAll = true;
-  }
-
-  /**
-   * Collapse the list to display the originally displayed items
-   */
-  viewLess() {
-    this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.options);
-    this.showingAll = false;
+    super();
   }
 
   /**
-   * Unsubscribe from the item subscription
+   * Get a specific page
+   * @param page  The page to fetch
    */
-  ngOnDestroy(): void {
-    if (this.itemSub) {
-      this.itemSub.unsubscribe();
-    }
+  getPage(page: number): Observable<RemoteData<PaginatedList<Item>>> {
+    return this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, Object.assign(this.options, { elementsPerPage: this.incrementBy, currentPage: page }));
   }
 }
diff --git a/src/app/+item-page/simple/related-items/related-items.component.html b/src/app/+item-page/simple/related-items/related-items.component.html
index dab85ee0e57d906880d600bca45297663175d284..11cedc40409f7ad8e0efacb5d6aa1d401a312ed7 100644
--- a/src/app/+item-page/simple/related-items/related-items.component.html
+++ b/src/app/+item-page/simple/related-items/related-items.component.html
@@ -1,11 +1,20 @@
-<ds-metadata-field-wrapper *ngIf="(items$ | async)?.payload?.page?.length > 0" [label]="label">
-    <ds-listable-object-component-loader *ngFor="let item of (items$ | async)?.payload?.page"
-                                         [object]="item" [viewMode]="viewMode">
-    </ds-listable-object-component-loader>
-  <div *ngIf="(items$ | async)?.payload?.page?.length < (items$ | async)?.payload?.totalElements" class="mt-2" id="view-more">
-    <a [routerLink]="" (click)="viewMore()">{{'item.page.related-items.view-more' | translate}}</a>
-  </div>
-  <div *ngIf="showingAll" class="mt-2" id="view-less">
-    <a [routerLink]="" (click)="viewLess()">{{'item.page.related-items.view-less' | translate}}</a>
-  </div>
+<ds-metadata-field-wrapper [label]="label">
+  <ng-container *ngFor="let objectPage of objects; let i = index">
+    <ng-container *ngVar="(objectPage | async) as itemsRD">
+      <ds-listable-object-component-loader *ngFor="let item of itemsRD?.payload?.page"
+                                           [object]="item" [viewMode]="viewMode">
+      </ds-listable-object-component-loader>
+      <ds-loading *ngIf="(i + 1) === objects.length && (itemsRD || i > 0) && !(itemsRD?.hasSucceeded && itemsRD?.payload && itemsRD?.payload?.page?.length > 0)" message="{{'loading.default' | translate}}"></ds-loading>
+      <div class="d-inline-block w-100 mt-2" *ngIf="(i + 1) === objects.length && itemsRD?.payload?.page?.length > 0">
+        <div *ngIf="itemsRD?.payload?.totalPages > objects.length" class="float-left" id="view-more">
+          <a [routerLink]="" (click)="increase()">{{'item.page.related-items.view-more' |
+            translate:{ amount: (itemsRD?.payload?.totalElements - (incrementBy * objects.length) < incrementBy) ? itemsRD?.payload?.totalElements - (incrementBy * objects.length) : incrementBy } }}</a>
+        </div>
+        <div *ngIf="objects.length > 1" class="float-right" id="view-less">
+          <a [routerLink]="" (click)="decrease()">{{'item.page.related-items.view-less' |
+            translate:{ amount: itemsRD?.payload?.page?.length } }}</a>
+        </div>
+      </div>
+    </ng-container>
+  </ng-container>
 </ds-metadata-field-wrapper>
diff --git a/src/app/+item-page/simple/related-items/related-items.component.spec.ts b/src/app/+item-page/simple/related-items/related-items.component.spec.ts
index 4a751a31b89798430690047584ee407699190929..5b1f33c64df0d9fe2b1eff520e069410a232fd54 100644
--- a/src/app/+item-page/simple/related-items/related-items.component.spec.ts
+++ b/src/app/+item-page/simple/related-items/related-items.component.spec.ts
@@ -9,6 +9,8 @@ import { createRelationshipsObservable } from '../item-types/shared/item.compone
 import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
 import { RelationshipService } from '../../../core/data/relationship.service';
 import { TranslateModule } from '@ngx-translate/core';
+import { VarDirective } from '../../../shared/utils/var.directive';
+import { of as observableOf } from 'rxjs';
 
 const parentItem: Item = Object.assign(new Item(), {
   bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
@@ -42,7 +44,7 @@ describe('RelatedItemsComponent', () => {
 
     TestBed.configureTestingModule({
       imports: [TranslateModule.forRoot()],
-      declarations: [RelatedItemsComponent],
+      declarations: [RelatedItemsComponent, VarDirective],
       providers: [
         { provide: RelationshipService, useValue: relationshipService }
       ],
@@ -65,31 +67,33 @@ describe('RelatedItemsComponent', () => {
     expect(fields.length).toBe(mockItems.length);
   });
 
-  describe('when viewMore is called', () => {
+  it('should contain one page of items', () => {
+    expect(comp.objects.length).toEqual(1);
+  });
+
+  describe('when increase is called', () => {
     beforeEach(() => {
-      comp.viewMore();
+      comp.increase();
     });
 
-    it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments', () => {
-      expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, comp.allOptions);
+    it('should add a new page to the list', () => {
+      expect(comp.objects.length).toEqual(2);
     });
 
-    it('should set showingAll to true', () => {
-      expect(comp.showingAll).toEqual(true);
+    it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments (second page)', () => {
+      expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, { elementsPerPage: comp.incrementBy, currentPage: 2 }));
     });
   });
 
-  describe('when viewLess is called', () => {
+  describe('when decrease is called', () => {
     beforeEach(() => {
-      comp.viewLess();
-    });
-
-    it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments', () => {
-      expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, comp.options);
+      // Add a second page
+      comp.objects.push(observableOf(undefined));
+      comp.decrease();
     });
 
-    it('should set showingAll to false', () => {
-      expect(comp.showingAll).toEqual(false);
+    it('should decrease the list of pages', () => {
+      expect(comp.objects.length).toEqual(1);
     });
   });
 
diff --git a/src/app/+search-page/configuration-search-page.component.ts b/src/app/+search-page/configuration-search-page.component.ts
index f7d7edcffc36d16c2b9c8a73e200dd2e1766e344..33d99a9cd2bc394e35cd020015c5ebf4e2d67498 100644
--- a/src/app/+search-page/configuration-search-page.component.ts
+++ b/src/app/+search-page/configuration-search-page.component.ts
@@ -34,6 +34,12 @@ export class ConfigurationSearchPageComponent extends SearchComponent implements
    */
   @Input() configuration: string;
 
+  /**
+   * The actual query for the fixed filter.
+   * If empty, the query will be determined by the route parameter called 'filter'
+   */
+  @Input() fixedFilterQuery: string;
+
   constructor(protected service: SearchService,
               protected sidebarService: SidebarService,
               protected windowService: HostWindowService,
diff --git a/src/app/+search-page/filtered-search-page.component.spec.ts b/src/app/+search-page/filtered-search-page.component.spec.ts
deleted file mode 100644
index e25cbd2e1266e3ca73cd6c218d613d967d1f063d..0000000000000000000000000000000000000000
--- a/src/app/+search-page/filtered-search-page.component.spec.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { FilteredSearchPageComponent } from './filtered-search-page.component';
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { configureSearchComponentTestingModule } from './search.component.spec';
-import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
-
-describe('FilteredSearchPageComponent', () => {
-  let comp: FilteredSearchPageComponent;
-  let fixture: ComponentFixture<FilteredSearchPageComponent>;
-  let searchConfigService: SearchConfigurationService;
-
-  beforeEach(async(() => {
-    configureSearchComponentTestingModule(FilteredSearchPageComponent);
-  }));
-
-  beforeEach(() => {
-    fixture = TestBed.createComponent(FilteredSearchPageComponent);
-    comp = fixture.componentInstance;
-    searchConfigService = (comp as any).searchConfigService;
-    fixture.detectChanges();
-  });
-});
diff --git a/src/app/+search-page/filtered-search-page.component.ts b/src/app/+search-page/filtered-search-page.component.ts
deleted file mode 100644
index c36dd0bf3ce766b42dbefc8c3f9c5a11af21ebdc..0000000000000000000000000000000000000000
--- a/src/app/+search-page/filtered-search-page.component.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import { HostWindowService } from '../shared/host-window.service';
-import { SearchService } from '../core/shared/search/search.service';
-import { SidebarService } from '../shared/sidebar/sidebar.service';
-import { SearchComponent } from './search.component';
-import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
-import { pushInOut } from '../shared/animations/push';
-import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
-import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
-import { Router } from '@angular/router';
-import { hasValue } from '../shared/empty.util';
-import { RouteService } from '../core/services/route.service';
-
-/**
- * This component renders a simple item page.
- * The route parameter 'id' is used to request the item it represents.
- * All fields of the item that should be displayed, are defined in its template.
- */
-@Component({
-  selector: 'ds-filtered-search-page',
-  styleUrls: ['./search.component.scss'],
-  templateUrl: './search.component.html',
-  changeDetection: ChangeDetectionStrategy.OnPush,
-  animations: [pushInOut],
-  providers: [
-    {
-      provide: SEARCH_CONFIG_SERVICE,
-      useClass: SearchConfigurationService
-    }
-  ]
-})
-
-export class FilteredSearchPageComponent extends SearchComponent implements OnInit {
-  /**
-   * The actual query for the fixed filter.
-   * If empty, the query will be determined by the route parameter called 'fixedFilterQuery'
-   */
-  @Input() fixedFilterQuery: string;
-
-  constructor(protected service: SearchService,
-              protected sidebarService: SidebarService,
-              protected windowService: HostWindowService,
-              @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
-              protected routeService: RouteService,
-              protected router: Router) {
-    super(service, sidebarService, windowService, searchConfigService, routeService, router);
-  }
-
-  /**
-   * Listening to changes in the paginated search options
-   * If something changes, update the search results
-   *
-   * Listen to changes in the scope
-   * If something changes, update the list of scopes for the dropdown
-   */
-  ngOnInit(): void {
-    super.ngOnInit();
-    if (hasValue(this.fixedFilterQuery)) {
-      this.routeService.setParameter('fixedFilterQuery', this.fixedFilterQuery);
-    }
-  }
-}
diff --git a/src/app/+search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts
index 083a1b441083612abf942d193e20dba55305d9c4..315e15a593b4ddaa277fb20d8e213834860d6b3e 100644
--- a/src/app/+search-page/search-page-routing.module.ts
+++ b/src/app/+search-page/search-page-routing.module.ts
@@ -1,7 +1,6 @@
 import { NgModule } from '@angular/core';
 import { RouterModule } from '@angular/router';
 
-import { SearchComponent } from './search.component';
 import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
 import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
 import { SearchPageComponent } from './search-page.component';
diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts
index ccea62eae5185ea62c6dd4c8dcc61c2745516943..0f96431bb1e927b162005bbaf24b0b5c014dca58 100644
--- a/src/app/+search-page/search-page.module.ts
+++ b/src/app/+search-page/search-page.module.ts
@@ -6,8 +6,6 @@ import { SearchPageRoutingModule } from './search-page-routing.module';
 import { SearchPageComponent } from './search-page.component';
 import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
 import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
-import { FilteredSearchPageComponent } from './filtered-search-page.component';
-import { EffectsModule } from '@ngrx/effects';
 import { SearchComponent } from './search.component';
 import { SearchTrackerComponent } from './search-tracker.component';
 import { StatisticsModule } from '../statistics/statistics.module';
@@ -15,7 +13,6 @@ import { StatisticsModule } from '../statistics/statistics.module';
 const components = [
   SearchPageComponent,
   SearchComponent,
-  FilteredSearchPageComponent,
   ConfigurationSearchPageComponent,
   SearchTrackerComponent,
 
diff --git a/src/app/+search-page/search.component.ts b/src/app/+search-page/search.component.ts
index 5b5787c91fb3ccfdf1e72f0030af957c49be78f7..b27ebf625f56bc3d9b6060e55936caac6c90367f 100644
--- a/src/app/+search-page/search.component.ts
+++ b/src/app/+search-page/search.component.ts
@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
-import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs';
+import { BehaviorSubject, Observable, Subscription } from 'rxjs';
 import { startWith, switchMap, } from 'rxjs/operators';
 import { PaginatedList } from '../core/data/paginated-list';
 import { RemoteData } from '../core/data/remote-data';
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 5085633a5b02c4cd50b5b12ca5ec04178340d20a..bd29db4ab8e469015655f01b83daeb939edb2c7b 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -27,6 +27,7 @@ export function getAdminModulePath() {
     RouterModule.forRoot([
       { path: '', redirectTo: '/home', pathMatch: 'full' },
       { path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' },
+      { path: 'community-list', loadChildren: './community-list-page/community-list-page.module#CommunityListPageModule' },
       { path: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
       { path: 'handle', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
       { path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule' },
diff --git a/src/app/app.effects.ts b/src/app/app.effects.ts
index 22df36e1ac3c025ee12270c8d15957fd0649ab8d..64573609c7a2cb8919c6ed15853cd4c70705f1ee 100644
--- a/src/app/app.effects.ts
+++ b/src/app/app.effects.ts
@@ -1,8 +1,8 @@
 import { StoreEffects } from './store.effects';
 import { NotificationsEffects } from './shared/notifications/notifications.effects';
 import { NavbarEffects } from './navbar/navbar.effects';
-import { RelationshipEffects } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects';
 import { SidebarEffects } from './shared/sidebar/sidebar-effects.service';
+import { RelationshipEffects } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects';
 
 export const appEffects = [
   StoreEffects,
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index a6d89d789a7155d14f0b993efa118d9ffe61286d..926575d711b715b85ce5c425a779556927e0753e 100755
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -37,9 +37,9 @@ import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.comp
 import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component';
 import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
 import { NavbarModule } from './navbar/navbar.module';
+import { ClientCookieService } from './core/services/client-cookie.service';
 import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module';
 import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module';
-import { ClientCookieService } from './core/services/client-cookie.service';
 
 export function getConfig() {
   return ENV_CONFIG;
diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts
index 8f841280f52e3b6ea207a6141b3ba85248a44f15..ad9247799b11d13684643c541de4232aa5a567a7 100644
--- a/src/app/app.reducer.ts
+++ b/src/app/app.reducer.ts
@@ -1,6 +1,7 @@
 import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
 import * as fromRouter from '@ngrx/router-store';
 import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
+import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer';
 import { formReducer, FormState } from './shared/form/form.reducer';
 import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
 import { sidebarFilterReducer, SidebarFiltersState } from './shared/sidebar/filter/sidebar-filter.reducer';
@@ -34,6 +35,7 @@ export interface AppState {
   objectSelection: ObjectSelectionListState;
   selectableLists: SelectableListsState;
   relationshipLists: NameVariantListsState;
+  communityList: CommunityListState;
 }
 
 export const appReducers: ActionReducerMap<AppState> = {
@@ -52,7 +54,8 @@ export const appReducers: ActionReducerMap<AppState> = {
   menus: menusReducer,
   objectSelection: objectSelectionReducer,
   selectableLists: selectableListReducer,
-  relationshipLists: nameVariantReducer
+  relationshipLists: nameVariantReducer,
+  communityList: CommunityListReducer,
 };
 
 export const routerStateSelector = (state: AppState) => state.router;
diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/community-list-page/community-list-datasource.ts b/src/app/community-list-page/community-list-datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3a9d9f2077d0e1f31c37e389b93f7fdf141200cd
--- /dev/null
+++ b/src/app/community-list-page/community-list-datasource.ts
@@ -0,0 +1,40 @@
+import { CommunityListService, FlatNode } from './community-list-service';
+import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections';
+import { BehaviorSubject, Observable, } from 'rxjs';
+import { finalize, take, } from 'rxjs/operators';
+
+/**
+ * DataSource object needed by a CDK Tree to render its nodes.
+ * The list of FlatNodes that this DataSource object represents gets created in the CommunityListService at
+ *    the beginning (initial page-limited top communities) and re-calculated any time the tree state changes
+ *    (a node gets expanded or page-limited result become larger by triggering a show more node)
+ */
+export class CommunityListDatasource implements DataSource<FlatNode> {
+
+  private communityList$ = new BehaviorSubject<FlatNode[]>([]);
+  public loading$ = new BehaviorSubject<boolean>(false);
+
+  constructor(private communityListService: CommunityListService) {
+  }
+
+  connect(collectionViewer: CollectionViewer): Observable<FlatNode[]> {
+    return this.communityList$.asObservable();
+  }
+
+  loadCommunities(expandedNodes: FlatNode[]) {
+    this.loading$.next(true);
+
+    this.communityListService.loadCommunities(expandedNodes).pipe(
+      take(1),
+      finalize(() => this.loading$.next(false)),
+    ).subscribe((flatNodes: FlatNode[]) => {
+      this.communityList$.next(flatNodes);
+    });
+  }
+
+  disconnect(collectionViewer: CollectionViewer): void {
+    this.communityList$.complete();
+    this.loading$.complete();
+  }
+
+}
diff --git a/src/app/community-list-page/community-list-page.component.html b/src/app/community-list-page/community-list-page.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..08accdc0e5b1ddf11502b86fb0afddab28d4f3da
--- /dev/null
+++ b/src/app/community-list-page/community-list-page.component.html
@@ -0,0 +1,4 @@
+<div class="container">
+  <h2>{{ 'communityList.title' | translate }}</h2>
+  <ds-community-list></ds-community-list>
+</div>
diff --git a/src/app/community-list-page/community-list-page.component.spec.ts b/src/app/community-list-page/community-list-page.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0aa4afce7f8f0dc9c324df6445541b2fbfb17201
--- /dev/null
+++ b/src/app/community-list-page/community-list-page.component.spec.ts
@@ -0,0 +1,41 @@
+import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
+
+import { CommunityListPageComponent } from './community-list-page.component';
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { MockTranslateLoader } from '../shared/mocks/mock-translate-loader';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+
+describe('CommunityListPageComponent', () => {
+  let component: CommunityListPageComponent;
+  let fixture: ComponentFixture<CommunityListPageComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        TranslateModule.forRoot({
+          loader: {
+            provide: TranslateLoader,
+            useClass: MockTranslateLoader
+          },
+        }),
+      ],
+      declarations: [CommunityListPageComponent],
+      providers: [
+        CommunityListPageComponent,
+      ],
+      schemas: [CUSTOM_ELEMENTS_SCHEMA],
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CommunityListPageComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', inject([CommunityListPageComponent], (comp: CommunityListPageComponent) => {
+    expect(comp).toBeTruthy();
+  }));
+
+});
diff --git a/src/app/community-list-page/community-list-page.component.ts b/src/app/community-list-page/community-list-page.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5ab3cce5de3e5cef4d5352b9494a5e87dc96b219
--- /dev/null
+++ b/src/app/community-list-page/community-list-page.component.ts
@@ -0,0 +1,13 @@
+import { Component } from '@angular/core';
+
+/**
+ * Page with title and the community list tree, as described in community-list.component;
+ * navigated to with community-list.page.routing.module
+ */
+@Component({
+  selector: 'ds-community-list-page',
+  templateUrl: './community-list-page.component.html',
+})
+export class CommunityListPageComponent {
+
+}
diff --git a/src/app/community-list-page/community-list-page.module.ts b/src/app/community-list-page/community-list-page.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e3914fe0314314ca626b02bbbffbecc2c45246e
--- /dev/null
+++ b/src/app/community-list-page/community-list-page.module.ts
@@ -0,0 +1,26 @@
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { SharedModule } from '../shared/shared.module';
+import { CommunityListPageComponent } from './community-list-page.component';
+import { CommunityListPageRoutingModule } from './community-list-page.routing.module';
+import { CommunityListComponent } from './community-list/community-list.component';
+import { CdkTreeModule } from '@angular/cdk/tree';
+
+/**
+ * The page which houses a title and the community list, as described in community-list.component
+ */
+@NgModule({
+  imports: [
+    CommonModule,
+    SharedModule,
+    CommunityListPageRoutingModule,
+    CdkTreeModule,
+  ],
+  declarations: [
+    CommunityListPageComponent,
+    CommunityListComponent
+  ]
+})
+export class CommunityListPageModule {
+
+}
diff --git a/src/app/community-list-page/community-list-page.routing.module.ts b/src/app/community-list-page/community-list-page.routing.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fe250cb96d64121a3e0674d5a64748c4a8637f26
--- /dev/null
+++ b/src/app/community-list-page/community-list-page.routing.module.ts
@@ -0,0 +1,26 @@
+import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { CdkTreeModule } from '@angular/cdk/tree';
+
+import { CommunityListPageComponent } from './community-list-page.component';
+import { CommunityListService } from './community-list-service';
+
+/**
+ * RouterModule to help navigate to the page with the community list tree
+ */
+@NgModule({
+  imports: [
+    RouterModule.forChild([
+      {
+        path: '',
+        component: CommunityListPageComponent,
+        pathMatch: 'full',
+        data: { title: 'communityList.tabTitle' }
+      }
+    ]),
+    CdkTreeModule,
+  ],
+  providers: [CommunityListService]
+})
+export class CommunityListPageRoutingModule {
+}
diff --git a/src/app/community-list-page/community-list-service.spec.ts b/src/app/community-list-page/community-list-service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a150277d200d9f90f6fd1f191aa259a519de93f2
--- /dev/null
+++ b/src/app/community-list-page/community-list-service.spec.ts
@@ -0,0 +1,574 @@
+import { of as observableOf } from 'rxjs';
+import { TestBed, inject, async } from '@angular/core/testing';
+import { Store } from '@ngrx/store';
+import { AppState } from '../app.reducer';
+import { MockStore } from '../shared/testing/mock-store';
+import { CommunityListService, FlatNode, toFlatNode } from './community-list-service';
+import { CollectionDataService } from '../core/data/collection-data.service';
+import { PaginatedList } from '../core/data/paginated-list';
+import { PageInfo } from '../core/shared/page-info.model';
+import { CommunityDataService } from '../core/data/community-data.service';
+import {
+  createFailedRemoteDataObject$,
+  createSuccessfulRemoteDataObject$
+} from '../shared/testing/utils';
+import { Community } from '../core/shared/community.model';
+import { Collection } from '../core/shared/collection.model';
+import { take } from 'rxjs/operators';
+import { FindListOptions } from '../core/data/request.models';
+
+describe('CommunityListService', () => {
+  let store: MockStore<AppState>;
+  const standardElementsPerPage = 2;
+  let collectionDataServiceStub: any;
+  let communityDataServiceStub: any;
+  const mockSubcommunities1Page1 = [Object.assign(new Community(), {
+    id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
+    uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
+  }),
+    Object.assign(new Community(), {
+      id: '59ee713b-ee53-4220-8c3f-9860dc84fe33',
+      uuid: '59ee713b-ee53-4220-8c3f-9860dc84fe33',
+    })
+  ];
+  const mockCollectionsPage1 = [
+    Object.assign(new Collection(), {
+      id: 'e9dbf393-7127-415f-8919-55be34a6e9ed',
+      uuid: 'e9dbf393-7127-415f-8919-55be34a6e9ed',
+      name: 'Collection 1'
+    }),
+    Object.assign(new Collection(), {
+      id: '59da2ff0-9bf4-45bf-88be-e35abd33f304',
+      uuid: '59da2ff0-9bf4-45bf-88be-e35abd33f304',
+      name: 'Collection 2'
+    })
+  ];
+  const mockCollectionsPage2 = [
+    Object.assign(new Collection(), {
+      id: 'a5159760-f362-4659-9e81-e3253ad91ede',
+      uuid: 'a5159760-f362-4659-9e81-e3253ad91ede',
+      name: 'Collection 3'
+    }),
+    Object.assign(new Collection(), {
+      id: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3',
+      uuid: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3',
+      name: 'Collection 4'
+    })
+  ];
+  const mockListOfTopCommunitiesPage1 = [
+    Object.assign(new Community(), {
+      id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+      uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+      subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)),
+      collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+    }),
+    Object.assign(new Community(), {
+      id: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+      uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+      subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+      collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])),
+    }),
+    Object.assign(new Community(), {
+      id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+      uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+      subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+      collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+    }),
+  ];
+  const mockListOfTopCommunitiesPage2 = [
+    Object.assign(new Community(), {
+      id: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6',
+      uuid: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6',
+      subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+      collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+    }),
+  ];
+  const mockTopCommunitiesWithChildrenArraysPage1 = [
+    {
+      id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+      uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+      subcommunities: mockSubcommunities1Page1,
+      collections: [],
+    },
+    {
+      id: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+      uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+      subcommunities: [],
+      collections: [...mockCollectionsPage1, ...mockCollectionsPage2],
+    },
+    {
+      id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+      uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+      subcommunities: [],
+      collections: [],
+    }];
+  const mockTopCommunitiesWithChildrenArraysPage2 = [
+    {
+      id: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6',
+      uuid: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6',
+      subcommunities: [],
+      collections: [],
+    }];
+
+  const allCommunities = [...mockTopCommunitiesWithChildrenArraysPage1, ...mockTopCommunitiesWithChildrenArraysPage2, ...mockSubcommunities1Page1];
+
+  let service: CommunityListService;
+
+  beforeEach(async(() => {
+    communityDataServiceStub = {
+      findTop(options: FindListOptions = {}) {
+        const allTopComs = [...mockListOfTopCommunitiesPage1, ...mockListOfTopCommunitiesPage2];
+        let currentPage = options.currentPage;
+        const elementsPerPage = 3;
+        if (currentPage === undefined) {
+          currentPage = 1
+        }
+        const startPageIndex = (currentPage - 1) * elementsPerPage;
+        let endPageIndex = (currentPage * elementsPerPage);
+        if (endPageIndex > allTopComs.length) {
+          endPageIndex = allTopComs.length;
+        }
+        return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), allTopComs.slice(startPageIndex, endPageIndex)));
+      },
+      findByParent(parentUUID: string, options: FindListOptions = {}) {
+        const foundCom = allCommunities.find((community) => (community.id === parentUUID));
+        let currentPage = options.currentPage;
+        let elementsPerPage = options.elementsPerPage;
+        if (currentPage === undefined) {
+          currentPage = 1
+        }
+        if (elementsPerPage === 0) {
+          return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), (foundCom.subcommunities as [Community])));
+        }
+        elementsPerPage = standardElementsPerPage;
+        if (foundCom !== undefined && foundCom.subcommunities !== undefined) {
+          const coms = foundCom.subcommunities as [Community];
+          const startPageIndex = (currentPage - 1) * elementsPerPage;
+          let endPageIndex = (currentPage * elementsPerPage);
+          if (endPageIndex > coms.length) {
+            endPageIndex = coms.length;
+          }
+          return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), coms.slice(startPageIndex, endPageIndex)));
+        } else {
+          return createFailedRemoteDataObject$();
+        }
+      }
+    };
+    collectionDataServiceStub = {
+      findByParent(parentUUID: string, options: FindListOptions = {}) {
+        const foundCom = allCommunities.find((community) => (community.id === parentUUID));
+        let currentPage = options.currentPage;
+        let elementsPerPage = options.elementsPerPage;
+        if (currentPage === undefined) {
+          currentPage = 1
+        }
+        if (elementsPerPage === 0) {
+          return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), (foundCom.collections as [Collection])));
+        }
+        elementsPerPage = standardElementsPerPage;
+        if (foundCom !== undefined && foundCom.collections !== undefined) {
+          const colls = foundCom.collections as [Collection];
+          const startPageIndex = (currentPage - 1) * elementsPerPage;
+          let endPageIndex = (currentPage * elementsPerPage);
+          if (endPageIndex > colls.length) {
+            endPageIndex = colls.length;
+          }
+          return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), colls.slice(startPageIndex, endPageIndex)));
+        } else {
+          return createFailedRemoteDataObject$();
+        }
+      }
+    };
+    TestBed.configureTestingModule({
+      providers: [CommunityListService,
+        { provide: CollectionDataService, useValue: collectionDataServiceStub },
+        { provide: CommunityDataService, useValue: communityDataServiceStub },
+        { provide: Store, useValue: MockStore },
+      ],
+    });
+    store = TestBed.get(Store);
+    service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store);
+  }));
+
+  afterAll(() => service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store));
+
+  it('should create', inject([CommunityListService], (serviceIn: CommunityListService) => {
+    expect(serviceIn).toBeTruthy();
+  }));
+
+  describe('getNextPageTopCommunities', () => {
+    describe('also load in second page of top communities', () => {
+      let flatNodeList;
+      describe('None expanded: should return list containing only flatnodes of the test top communities page 1 and 2', () => {
+        let findTopSpy;
+        beforeEach(() => {
+          findTopSpy = spyOn(communityDataServiceStub, 'findTop').and.callThrough();
+          service.getNextPageTopCommunities();
+
+          const sub = service.loadCommunities(null)
+            .subscribe((value) => flatNodeList = value);
+          sub.unsubscribe();
+        });
+        it('flatnode list should contain just flatnodes of top community list page 1 and 2', () => {
+          expect(findTopSpy).toHaveBeenCalled();
+          expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockListOfTopCommunitiesPage2.length);
+          mockListOfTopCommunitiesPage1.map((community) => {
+            expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy();
+          });
+          mockListOfTopCommunitiesPage2.map((community) => {
+            expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy();
+          });
+        });
+      });
+    });
+  });
+
+  describe('loadCommunities', () => {
+    describe('should transform all communities in a list of flatnodes with possible subcoms and collections as subflatnodes if they\'re expanded', () => {
+      let flatNodeList;
+      describe('None expanded: should return list containing only flatnodes of the test top communities', () => {
+        beforeEach(() => {
+          const sub = service.loadCommunities(null)
+            .pipe(take(1)).subscribe((value) => flatNodeList = value);
+          sub.unsubscribe();
+        });
+        it('length of flatnode list should be as big as top community list', () => {
+          expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length);
+        });
+        it('flatnode list should contain flatNode representations of top communities', () => {
+          mockListOfTopCommunitiesPage1.map((community) => {
+            expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy();
+          });
+        });
+        it('none of the flatnodes in the list should be expanded', () => {
+          flatNodeList.map((flatnode: FlatNode) => {
+            expect(flatnode.isExpanded).toEqual(false);
+          });
+        });
+      });
+      describe('All top expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => {
+        beforeEach(() => {
+          const expandedNodes = [];
+          mockListOfTopCommunitiesPage1.map((community: Community) => {
+            const communityFlatNode = toFlatNode(community, observableOf(true), 0, true, null);
+            communityFlatNode.currentCollectionPage = 1;
+            communityFlatNode.currentCommunityPage = 1;
+            expandedNodes.push(communityFlatNode);
+          });
+          const sub = service.loadCommunities(expandedNodes)
+            .pipe(take(1)).subscribe((value) => flatNodeList = value);
+          sub.unsubscribe();
+        });
+        it('length of flatnode list should be as big as top community list and size of its possible page-limited children', () => {
+          expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockSubcommunities1Page1.length + mockSubcommunities1Page1.length);
+        });
+        it('flatnode list should contain flatNode representations of all page-limited children', () => {
+          mockSubcommunities1Page1.map((subcommunity) => {
+            expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy();
+          });
+          mockCollectionsPage1.map((collection) => {
+            expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy();
+          });
+        });
+      });
+      describe('Just first top comm expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => {
+        beforeEach(() => {
+          const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[0], observableOf(true), 0, true, null);
+          communityFlatNode.currentCollectionPage = 1;
+          communityFlatNode.currentCommunityPage = 1;
+          const expandedNodes = [communityFlatNode];
+          const sub = service.loadCommunities(expandedNodes)
+            .pipe(take(1)).subscribe((value) => flatNodeList = value);
+          sub.unsubscribe();
+        });
+        it('length of flatnode list should be as big as top community list and size of page-limited children of first top community', () => {
+          expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockSubcommunities1Page1.length);
+        });
+        it('flatnode list should contain flatNode representations of all page-limited children of first top community', () => {
+          mockSubcommunities1Page1.map((subcommunity) => {
+            expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy();
+          });
+        });
+      });
+      describe('Just second top comm expanded, collections at page 2: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => {
+        beforeEach(() => {
+          const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[1], observableOf(true), 0, true, null);
+          communityFlatNode.currentCollectionPage = 2;
+          communityFlatNode.currentCommunityPage = 1;
+          const expandedNodes = [communityFlatNode];
+          const sub = service.loadCommunities(expandedNodes)
+            .pipe(take(1)).subscribe((value) => flatNodeList = value);
+          sub.unsubscribe();
+        });
+        it('length of flatnode list should be as big as top community list and size of page-limited children of second top community', () => {
+          expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockCollectionsPage1.length + mockCollectionsPage2.length);
+        });
+        it('flatnode list should contain flatNode representations of all page-limited children of first top community', () => {
+          mockCollectionsPage1.map((collection) => {
+            expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy();
+          });
+          mockCollectionsPage2.map((collection) => {
+            expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy();
+          });
+        });
+      });
+    });
+  });
+
+  describe('transformListOfCommunities', () => {
+    describe('should transform list of communities in a list of flatnodes with possible subcoms and collections as subflatnodes if they\'re expanded', () => {
+      describe('list of communities with possible children', () => {
+        const listOfCommunities = mockListOfTopCommunitiesPage1;
+        let flatNodeList;
+        describe('None expanded: should return list containing only flatnodes of the communities in the test list', () => {
+          beforeEach(() => {
+            const sub = service.transformListOfCommunities(new PaginatedList(new PageInfo(), listOfCommunities), 0, null, null)
+              .pipe(take(1)).subscribe((value) => flatNodeList = value);
+            sub.unsubscribe();
+          });
+          it('length of flatnode list should be as big as community test list', () => {
+            expect(flatNodeList.length).toEqual(listOfCommunities.length);
+          });
+          it('flatnode list should contain flatNode representations of all communities from test list', () => {
+            listOfCommunities.map((community) => {
+              expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy();
+            });
+          });
+          it('none of the flatnodes in the list should be expanded', () => {
+            flatNodeList.map((flatnode: FlatNode) => {
+              expect(flatnode.isExpanded).toEqual(false);
+            });
+          });
+        });
+        describe('All top expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => {
+          beforeEach(() => {
+            const expandedNodes = [];
+            listOfCommunities.map((community: Community) => {
+              const communityFlatNode = toFlatNode(community, observableOf(true), 0, true, null);
+              communityFlatNode.currentCollectionPage = 1;
+              communityFlatNode.currentCommunityPage = 1;
+              expandedNodes.push(communityFlatNode);
+            });
+            const sub = service.transformListOfCommunities(new PaginatedList(new PageInfo(), listOfCommunities), 0, null, expandedNodes)
+              .pipe(take(1)).subscribe((value) => flatNodeList = value);
+            sub.unsubscribe();
+          });
+          it('length of flatnode list should be as big as community test list and size of its possible children', () => {
+            expect(flatNodeList.length).toEqual(listOfCommunities.length + mockSubcommunities1Page1.length + mockSubcommunities1Page1.length);
+          });
+          it('flatnode list should contain flatNode representations of all children', () => {
+            mockSubcommunities1Page1.map((subcommunity) => {
+              expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy();
+            });
+            mockSubcommunities1Page1.map((collection) => {
+              expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy();
+            });
+          });
+        });
+      });
+    });
+
+  });
+
+  describe('transformCommunity', () => {
+    describe('should transform community in list of flatnodes with possible subcoms and collections as subflatnodes if its expanded', () => {
+      describe('topcommunity without subcoms or collections, unexpanded', () => {
+        const communityWithNoSubcomsOrColls = Object.assign(new Community(), {
+          id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+          uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+          subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+          collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+          metadata: {
+            'dc.description': [{ language: 'en_US', value: 'no subcoms, 2 coll' }],
+            'dc.title': [{ language: 'en_US', value: 'Community 2' }]
+          }
+        });
+        let flatNodeList;
+        describe('should return list containing only flatnode corresponding to that community', () => {
+          beforeEach(() => {
+            const sub = service.transformCommunity(communityWithNoSubcomsOrColls, 0, null, null)
+              .pipe(take(1)).subscribe((value) => flatNodeList = value);
+            sub.unsubscribe();
+          });
+          it('length of flatnode list should be 1', () => {
+            expect(flatNodeList.length).toEqual(1);
+          });
+          it('flatnode list only element should be flatNode of test community', () => {
+            expect(flatNodeList[0].id).toEqual(communityWithNoSubcomsOrColls.id);
+          });
+          it('flatnode from test community is not expanded', () => {
+            expect(flatNodeList[0].isExpanded).toEqual(false);
+          });
+        });
+      });
+      describe('topcommunity with subcoms or collections, unexpanded', () => {
+        const communityWithSubcoms = Object.assign(new Community(), {
+          id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+          uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+          subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)),
+          collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+          metadata: {
+            'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }],
+            'dc.title': [{ language: 'en_US', value: 'Community 1' }]
+          }
+        });
+        let flatNodeList;
+        describe('should return list containing only flatnode corresponding to that community', () => {
+          beforeAll(() => {
+            const sub = service.transformCommunity(communityWithSubcoms, 0, null, null)
+              .pipe(take(1)).subscribe((value) => flatNodeList = value);
+            sub.unsubscribe();
+          });
+          it('length of flatnode list should be 1', () => {
+            expect(flatNodeList.length).toEqual(1);
+          });
+          it('flatnode list only element should be flatNode of test community', () => {
+            expect(flatNodeList[0].id).toEqual(communityWithSubcoms.id);
+          });
+          it('flatnode from test community is not expanded', () => {
+            expect(flatNodeList[0].isExpanded).toEqual(false);
+          });
+        });
+      });
+      describe('topcommunity with subcoms, expanded, first page for all', () => {
+        describe('should return list containing flatnodes of that community, its possible subcommunities and its possible collections', () => {
+          const communityWithSubcoms = Object.assign(new Community(), {
+            id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+            uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+            subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)),
+            collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+            metadata: {
+              'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }],
+              'dc.title': [{ language: 'en_US', value: 'Community 1' }]
+            }
+          });
+          let flatNodeList;
+          beforeEach(() => {
+            const communityFlatNode = toFlatNode(communityWithSubcoms, observableOf(true), 0, true, null);
+            communityFlatNode.currentCollectionPage = 1;
+            communityFlatNode.currentCommunityPage = 1;
+            const expandedNodes = [communityFlatNode];
+            const sub = service.transformCommunity(communityWithSubcoms, 0, null, expandedNodes)
+              .pipe(take(1)).subscribe((value) => flatNodeList = value);
+            sub.unsubscribe();
+          });
+          it('list of flatnodes is length is  1 + nrOfSubcoms & first flatnode is of expanded test community', () => {
+            expect(flatNodeList.length).toEqual(1 + mockSubcommunities1Page1.length);
+            expect(flatNodeList[0].isExpanded).toEqual(true);
+            expect(flatNodeList[0].id).toEqual(communityWithSubcoms.id);
+          });
+          it('list of flatnodes contains flatnodes for all subcoms of test community', () => {
+            mockSubcommunities1Page1.map((subcommunity) => {
+              expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy();
+            });
+          });
+          it('the subcoms of the test community are a level higher than the parent community', () => {
+            mockSubcommunities1Page1.map((subcommunity) => {
+              expect((flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).level).toEqual(flatNodeList[0].level + 1);
+            });
+          });
+        });
+      });
+      describe('topcommunity with collections, expanded, on second page of collections', () => {
+        describe('should return list containing flatnodes of that community, its collections of the first two pages', () => {
+          const communityWithCollections = Object.assign(new Community(), {
+            id: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+            uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+            subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+            collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])),
+            metadata: {
+              'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }],
+              'dc.title': [{ language: 'en_US', value: 'Community 1' }]
+            }
+          });
+          let flatNodeList;
+          beforeEach(() => {
+            const communityFlatNode = toFlatNode(communityWithCollections, observableOf(true), 0, true, null);
+            communityFlatNode.currentCollectionPage = 2;
+            communityFlatNode.currentCommunityPage = 1;
+            const expandedNodes = [communityFlatNode];
+            const sub = service.transformCommunity(communityWithCollections, 0, null, expandedNodes)
+              .pipe(take(1)).subscribe((value) => flatNodeList = value);
+            sub.unsubscribe();
+          });
+          it('list of flatnodes is length is  1 + nrOfCollections & first flatnode is of expanded test community', () => {
+            expect(flatNodeList.length).toEqual(1 + mockCollectionsPage1.length + mockCollectionsPage2.length);
+            expect(flatNodeList[0].isExpanded).toEqual(true);
+            expect(flatNodeList[0].id).toEqual(communityWithCollections.id);
+          });
+          it('list of flatnodes contains flatnodes for all subcolls (first 2 pages) of test community', () => {
+            mockCollectionsPage1.map((collection) => {
+              expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy();
+            });
+            mockCollectionsPage2.map((collection) => {
+              expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy();
+            })
+          });
+          it('the collections of the test community are a level higher than the parent community', () => {
+            mockCollectionsPage1.map((collection) => {
+              expect((flatNodeList.find((flatnode) => (flatnode.id === collection.id))).level).toEqual(flatNodeList[0].level + 1);
+            });
+            mockCollectionsPage2.map((collection) => {
+              expect((flatNodeList.find((flatnode) => (flatnode.id === collection.id))).level).toEqual(flatNodeList[0].level + 1);
+            })
+          });
+        });
+      });
+    });
+
+  });
+
+  describe('getIsExpandable', () => {
+    describe('should return true', () => {
+      it('if community has subcommunities', () => {
+        const communityWithSubcoms = Object.assign(new Community(), {
+          id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+          uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+          subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)),
+          collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+          metadata: {
+            'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }],
+            'dc.title': [{ language: 'en_US', value: 'Community 1' }]
+          }
+        });
+        service.getIsExpandable(communityWithSubcoms).pipe(take(1)).subscribe((result) => {
+          expect(result).toEqual(true);
+        });
+      });
+      it('if community has collections', () => {
+        const communityWithCollections = Object.assign(new Community(), {
+          id: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+          uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+          subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+          collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockCollectionsPage1)),
+          metadata: {
+            'dc.description': [{ language: 'en_US', value: 'no subcoms, 2 coll' }],
+            'dc.title': [{ language: 'en_US', value: 'Community 2' }]
+          }
+        });
+        service.getIsExpandable(communityWithCollections).pipe(take(1)).subscribe((result) => {
+          expect(result).toEqual(true);
+        });
+      });
+    });
+    describe('should return false', () => {
+      it('if community has neither subcommunities nor collections', () => {
+        const communityWithNoSubcomsOrColls = Object.assign(new Community(), {
+          id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+          uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+          subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+          collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+          metadata: {
+            'dc.description': [{ language: 'en_US', value: 'no subcoms, no coll' }],
+            'dc.title': [{ language: 'en_US', value: 'Community 3' }]
+          }
+        });
+        service.getIsExpandable(communityWithNoSubcomsOrColls).pipe(take(1)).subscribe((result) => {
+          expect(result).toEqual(false);
+        });
+      });
+    });
+
+  });
+
+});
diff --git a/src/app/community-list-page/community-list-service.ts b/src/app/community-list-page/community-list-service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a25dbd2689604626c1a958a3bf9a8fd4305cd048
--- /dev/null
+++ b/src/app/community-list-page/community-list-service.ts
@@ -0,0 +1,335 @@
+import { Injectable } from '@angular/core';
+import { createSelector, Store } from '@ngrx/store';
+import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
+import { Observable, of as observableOf } from 'rxjs';
+import { AppState } from '../app.reducer';
+import { CommunityDataService } from '../core/data/community-data.service';
+import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
+import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
+import { catchError, filter, map, switchMap, take } from 'rxjs/operators';
+import { Community } from '../core/shared/community.model';
+import { Collection } from '../core/shared/collection.model';
+import { hasValue, isNotEmpty } from '../shared/empty.util';
+import { RemoteData } from '../core/data/remote-data';
+import { PaginatedList } from '../core/data/paginated-list';
+import { getCommunityPageRoute } from '../+community-page/community-page-routing.module';
+import { getCollectionPageRoute } from '../+collection-page/collection-page-routing.module';
+import { CollectionDataService } from '../core/data/collection-data.service';
+import { CommunityListSaveAction } from './community-list.actions';
+import { CommunityListState } from './community-list.reducer';
+
+/**
+ * Each node in the tree is represented by a flatNode which contains info about the node itself and its position and
+ *  state in the tree. There are nodes representing communities, collections and show more links.
+ */
+export interface FlatNode {
+  isExpandable$: Observable<boolean>;
+  name: string;
+  id: string;
+  level: number;
+  isExpanded?: boolean;
+  parent?: FlatNode;
+  payload: Community | Collection | ShowMoreFlatNode;
+  isShowMoreNode: boolean;
+  route?: string;
+  currentCommunityPage?: number;
+  currentCollectionPage?: number;
+}
+
+/**
+ * The show more links in the community tree are also represented by a flatNode so we know where in
+ *  the tree it should be rendered an who its parent is (needed for the action resulting in clicking this link)
+ */
+export class ShowMoreFlatNode {
+}
+
+// Helper method to combine an flatten an array of observables of flatNode arrays
+export const combineAndFlatten = (obsList: Array<Observable<FlatNode[]>>): Observable<FlatNode[]> =>
+  observableCombineLatest(...obsList).pipe(
+    map((matrix: FlatNode[][]) =>
+      matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList]))
+  );
+
+/**
+ * Creates a flatNode from a community or collection
+ * @param c               The community or collection this flatNode represents
+ * @param isExpandable    Whether or not this node is expandable (true if it has children)
+ * @param level           Level indicating how deep in the tree this node should be rendered
+ * @param isExpanded      Whether or not this node already is expanded
+ * @param parent          Parent of this node (flatNode representing its parent community)
+ */
+export const toFlatNode = (
+  c: Community | Collection,
+  isExpandable: Observable<boolean>,
+  level: number,
+  isExpanded: boolean,
+  parent?: FlatNode
+): FlatNode => ({
+  isExpandable$: isExpandable,
+  name: c.name,
+  id: c.id,
+  level: level,
+  isExpanded,
+  parent,
+  payload: c,
+  isShowMoreNode: false,
+  route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id),
+});
+
+/**
+ * Creates a show More flatnode where only the level and parent are of importance
+ */
+export const showMoreFlatNode = (
+  id: string,
+  level: number,
+  parent: FlatNode
+): FlatNode => ({
+  isExpandable$: observableOf(false),
+  name: 'Show More Flatnode',
+  id: id,
+  level: level,
+  isExpanded: false,
+  parent: parent,
+  payload: new ShowMoreFlatNode(),
+  isShowMoreNode: true,
+});
+
+// Selectors the get the communityList data out of the store
+const communityListStateSelector = (state: AppState) => state.communityList;
+const expandedNodesSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.expandedNodes);
+const loadingNodeSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.loadingNode);
+
+/**
+ * Service class for the community list, responsible for the creating of the flat list used by communityList dataSource
+ *  and connection to the store to retrieve and save the state of the community list
+ */
+// tslint:disable-next-line:max-classes-per-file
+@Injectable()
+export class CommunityListService {
+
+  // page-limited list of top-level communities
+  payloads$: Array<Observable<PaginatedList<Community>>>;
+
+  topCommunitiesConfig: PaginationComponentOptions;
+  topCommunitiesSortConfig: SortOptions;
+
+  maxSubCommunitiesPerPage: number;
+  maxCollectionsPerPage: number;
+
+  constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService,
+              private store: Store<any>) {
+    this.topCommunitiesConfig = new PaginationComponentOptions();
+    this.topCommunitiesConfig.id = 'top-level-pagination';
+    this.topCommunitiesConfig.pageSize = 10;
+    this.topCommunitiesConfig.currentPage = 1;
+    this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC);
+    this.initTopCommunityList();
+
+    this.maxSubCommunitiesPerPage = 3;
+    this.maxCollectionsPerPage = 3;
+  }
+
+  saveCommunityListStateToStore(expandedNodes: FlatNode[], loadingNode: FlatNode): void {
+    this.store.dispatch(new CommunityListSaveAction(expandedNodes, loadingNode));
+  }
+
+  getExpandedNodesFromStore(): Observable<FlatNode[]> {
+    return this.store.select(expandedNodesSelector);
+  }
+
+  getLoadingNodeFromStore(): Observable<FlatNode> {
+    return this.store.select(loadingNodeSelector);
+  }
+
+  /**
+   * Increases the payload so it contains the next page of top level communities
+   */
+  getNextPageTopCommunities(): void {
+    this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1;
+    this.payloads$ = [...this.payloads$, this.communityDataService.findTop({
+      currentPage: this.topCommunitiesConfig.currentPage,
+      elementsPerPage: this.topCommunitiesConfig.pageSize,
+      sort: {
+        field: this.topCommunitiesSortConfig.field,
+        direction: this.topCommunitiesSortConfig.direction
+      }
+    }).pipe(
+      take(1),
+      map((results) => results.payload),
+    )];
+  }
+
+  /**
+   * Gets all top communities, limited by page, and transforms this in a list of flatNodes.
+   * @param expandedNodes     List of expanded nodes; if a node is not expanded its subCommunities and collections need
+   *                            not be added to the list
+   */
+  loadCommunities(expandedNodes: FlatNode[]): Observable<FlatNode[]> {
+    const res = this.payloads$.map((payload) => {
+      return payload.pipe(
+        take(1),
+        switchMap((result: PaginatedList<Community>) => {
+          return this.transformListOfCommunities(result, 0, null, expandedNodes);
+        }),
+        catchError(() => observableOf([])),
+      );
+    });
+    return combineAndFlatten(res);
+  };
+
+  /**
+   * Puts the initial top level communities in a list to be called upon
+   */
+  private initTopCommunityList(): void {
+    this.payloads$ = [this.communityDataService.findTop({
+      currentPage: this.topCommunitiesConfig.currentPage,
+      elementsPerPage: this.topCommunitiesConfig.pageSize,
+      sort: {
+        field: this.topCommunitiesSortConfig.field,
+        direction: this.topCommunitiesSortConfig.direction
+      }
+    }).pipe(
+      take(1),
+      map((results) => results.payload),
+    )];
+  }
+
+  /**
+   * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity
+   * @param listOfPaginatedCommunities    Paginated list of communities to be transformed
+   * @param level                         Level the tree is currently at
+   * @param parent                        FlatNode of the parent of this list of communities
+   * @param expandedNodes                 List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list
+   */
+  public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList<Community>,
+                                    level: number,
+                                    parent: FlatNode,
+                                    expandedNodes: FlatNode[]): Observable<FlatNode[]> {
+    if (isNotEmpty(listOfPaginatedCommunities.page)) {
+      let currentPage = this.topCommunitiesConfig.currentPage;
+      if (isNotEmpty(parent)) {
+        currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage;
+      }
+      const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage));
+      let obsList = listOfPaginatedCommunities.page
+        .map((community: Community) => {
+          return this.transformCommunity(community, level, parent, expandedNodes)
+        });
+      if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) {
+        obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])];
+      }
+
+      return combineAndFlatten(obsList);
+    } else {
+      return observableOf([]);
+    }
+  }
+
+  /**
+   * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself,
+   *      followed by flatNodes of its possible subcommunities and collection
+   * It gets called recursively for each subcommunity to add its subcommunities and collections to the list
+   * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections.
+   * @param community         Community being transformed
+   * @param level             Depth of the community in the list, subcommunities and collections go one level deeper
+   * @param parent            Flatnode of the parent community
+   * @param expandedNodes     List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections
+   */
+  public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable<FlatNode[]> {
+    let isExpanded = false;
+    if (isNotEmpty(expandedNodes)) {
+      isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id)));
+    }
+
+    const isExpandable$ = this.getIsExpandable(community);
+
+    const communityFlatNode = toFlatNode(community, isExpandable$, level, isExpanded, parent);
+
+    let obsList = [observableOf([communityFlatNode])];
+
+    if (isExpanded) {
+      const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage;
+      let subcoms = [];
+      for (let i = 1; i <= currentCommunityPage; i++) {
+        const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, {
+          elementsPerPage: this.maxSubCommunitiesPerPage,
+          currentPage: i
+        })
+          .pipe(
+            filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
+            take(1),
+            switchMap((rd: RemoteData<PaginatedList<Community>>) =>
+              this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes))
+          );
+
+        subcoms = [...subcoms, nextSetOfSubcommunitiesPage];
+      }
+
+      obsList = [...obsList, combineAndFlatten(subcoms)];
+
+      const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage;
+      let collections = [];
+      for (let i = 1; i <= currentCollectionPage; i++) {
+        const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, {
+          elementsPerPage: this.maxCollectionsPerPage,
+          currentPage: i
+        })
+          .pipe(
+            filter((rd: RemoteData<PaginatedList<Collection>>) => rd.hasSucceeded),
+            take(1),
+            map((rd: RemoteData<PaginatedList<Collection>>) => {
+              let nodes = rd.payload.page
+                .map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode));
+              if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) {
+                nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)];
+              }
+              return nodes;
+            }),
+          );
+        collections = [...collections, nextSetOfCollectionsPage];
+      }
+      obsList = [...obsList, combineAndFlatten(collections)];
+    }
+
+    return combineAndFlatten(obsList);
+  }
+
+  /**
+   * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0
+   *      Returns an observable that combines the result.payload.totalElements fo the observables that the
+   *          respective services return when queried
+   * @param community     Community being checked whether it is expandable (if it has subcommunities or collections)
+   */
+  public getIsExpandable(community: Community): Observable<boolean> {
+    let hasSubcoms$: Observable<boolean>;
+    let hasColls$: Observable<boolean>;
+    hasSubcoms$ = this.communityDataService.findByParent(community.uuid, { elementsPerPage: 1 })
+      .pipe(
+        filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
+        take(1),
+        map((results) => results.payload.totalElements > 0),
+      );
+
+    hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 })
+      .pipe(
+        filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
+        take(1),
+        map((results) => results.payload.totalElements > 0),
+      );
+
+    let hasChildren$: Observable<boolean>;
+    hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe(
+      take(1),
+      map((result: [boolean]) => {
+        if (result[0] || result[1]) {
+          return true;
+        } else {
+          return false;
+        }
+      })
+    );
+
+    return hasChildren$;
+  }
+
+}
diff --git a/src/app/community-list-page/community-list.actions.ts b/src/app/community-list-page/community-list.actions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bfce6fba34d676f2d4e1a9d0899950b5a400c66e
--- /dev/null
+++ b/src/app/community-list-page/community-list.actions.ts
@@ -0,0 +1,35 @@
+import { Action } from '@ngrx/store';
+import { type } from '../shared/ngrx/type';
+import { FlatNode } from './community-list-service';
+
+/**
+ * All the action types of the community-list
+ */
+
+export const CommunityListActionTypes = {
+  SAVE: type('dspace/community-list-page/SAVE')
+};
+
+/**
+ * Community list SAVE action
+ */
+export class CommunityListSaveAction implements Action {
+
+  type = CommunityListActionTypes.SAVE;
+
+  payload: {
+    expandedNodes: FlatNode[];
+    loadingNode: FlatNode;
+  };
+
+  constructor(expandedNodes: FlatNode[], loadingNode: FlatNode) {
+    this.payload = { expandedNodes, loadingNode }
+  }
+};
+
+/**
+ * Export a type alias of all actions in this action group
+ * so that reducers can easily compose action types
+ */
+
+export type CommunityListActions = CommunityListSaveAction;
diff --git a/src/app/community-list-page/community-list.reducer.spec.ts b/src/app/community-list-page/community-list.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..63eaaccc036fc42eea1a84b51ce02efaabe2bdb5
--- /dev/null
+++ b/src/app/community-list-page/community-list.reducer.spec.ts
@@ -0,0 +1,45 @@
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { PaginatedList } from '../core/data/paginated-list';
+import { Community } from '../core/shared/community.model';
+import { PageInfo } from '../core/shared/page-info.model';
+import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils';
+import { toFlatNode } from './community-list-service';
+import { CommunityListSaveAction } from './community-list.actions';
+import { CommunityListReducer } from './community-list.reducer';
+
+describe('communityListReducer', () => {
+  const mockSubcommunities1Page1 = [Object.assign(new Community(), {
+    id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
+    uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
+    name: 'subcommunity1',
+  })];
+  const mockFlatNodeOfCommunity = toFlatNode(
+    Object.assign(new Community(), {
+      id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+      uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+      subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)),
+      collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+      name: 'community1',
+    }), observableOf(true), 0, false, null
+  );
+
+  it ('should set init state of the expandedNodes and loadingNode', () => {
+    const state = {
+      expandedNodes: [],
+      loadingNode: null,
+    };
+    const action = new CommunityListSaveAction([], null);
+    const newState = CommunityListReducer(null, action);
+    expect(newState).toEqual(state);
+  });
+
+  it ('should save new state of the expandedNodes and loadingNode at a save action', () => {
+    const state = {
+      expandedNodes: [mockFlatNodeOfCommunity],
+      loadingNode: null,
+    };
+    const action = new CommunityListSaveAction([mockFlatNodeOfCommunity], null);
+    const newState = CommunityListReducer(null, action);
+    expect(newState).toEqual(state);
+  });
+});
diff --git a/src/app/community-list-page/community-list.reducer.ts b/src/app/community-list-page/community-list.reducer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b455fc496a7ce9c42eb6f5cb5f2ce22410597079
--- /dev/null
+++ b/src/app/community-list-page/community-list.reducer.ts
@@ -0,0 +1,36 @@
+import { FlatNode } from './community-list-service';
+import { CommunityListActions, CommunityListActionTypes, CommunityListSaveAction } from './community-list.actions';
+
+/**
+ * States we wish to put in store concerning the community list
+ */
+export interface CommunityListState {
+  expandedNodes: FlatNode[];
+  loadingNode: FlatNode;
+}
+
+/**
+ * Initial starting state of the list of expandedNodes and the current loading node of the community list
+ */
+const initialState: CommunityListState = {
+  expandedNodes: [],
+  loadingNode: null,
+};
+
+/**
+ * Reducer to interact with store concerning objects for the community list
+ * @constructor
+ */
+export function CommunityListReducer(state = initialState, action: CommunityListActions) {
+  switch (action.type) {
+    case CommunityListActionTypes.SAVE: {
+      return Object.assign({}, state, {
+        expandedNodes: (action as CommunityListSaveAction).payload.expandedNodes,
+        loadingNode: (action as CommunityListSaveAction).payload.loadingNode,
+      })
+    }
+    default: {
+      return state;
+    }
+  }
+}
diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..c179715bf153de2a381423d08454cb77585f64b4
--- /dev/null
+++ b/src/app/community-list-page/community-list/community-list.component.html
@@ -0,0 +1,91 @@
+<ds-loading *ngIf="(dataSource.loading$ | async) && loadingNode === undefined " class="ds-loading"></ds-loading>
+
+<cdk-tree [dataSource]="dataSource" [treeControl]="treeControl">
+  <!-- This is the tree node template for show more node -->
+  <cdk-tree-node *cdkTreeNodeDef="let node; when: isShowMore" cdkTreeNodePadding
+                 class="example-tree-node show-more-node">
+    <div class="btn-group">
+      <button type="button" class="btn btn-default" cdkTreeNodeToggle>
+        <span class="fa fa-chevron-right invisible" aria-hidden="true"></span>
+      </button>
+      <div class="align-middle pt-2">
+        <a *ngIf="node!==loadingNode" [routerLink]="" (click)="getNextPage(node)"
+           class="btn btn-outline-secondary btn-sm">
+          {{ 'communityList.showMore' | translate }}
+        </a>
+        <ds-loading *ngIf="node===loadingNode && dataSource.loading$ | async" class="ds-loading"></ds-loading>
+      </div>
+    </div>
+    <div class="text-muted" cdkTreeNodePadding>
+      <div class="d-flex">
+      </div>
+    </div>
+  </cdk-tree-node>
+  <!-- This is the tree node template for expandable nodes (coms and subcoms with children) -->
+  <cdk-tree-node *cdkTreeNodeDef="let node; when: hasChild" cdkTreeNodePadding
+                 class="example-tree-node expandable-node">
+    <div class="btn-group">
+      <button type="button" class="btn btn-default" cdkTreeNodeToggle
+              [attr.aria-label]="'toggle ' + node.name"
+              (click)="toggleExpanded(node)"
+              [ngClass]="(node.isExpandable$ | async) ? 'visible' : 'invisible'">
+        <span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
+              aria-hidden="true"></span>
+      </button>
+      <h5 class="align-middle pt-2">
+        <a [routerLink]="node.route" class="lead">
+          {{node.name}}
+        </a>
+      </h5>
+    </div>
+    <ds-truncatable [id]="node.id">
+      <div class="text-muted" cdkTreeNodePadding>
+        <div class="d-flex" *ngIf="node.payload.shortDescription">
+          <button type="button" class="btn btn-default invisible">
+            <span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
+                  aria-hidden="true"></span>
+          </button>
+          <ds-truncatable-part [id]="node.id" [minLines]="3">
+            <span>{{node.payload.shortDescription}}</span>
+          </ds-truncatable-part>
+        </div>
+      </div>
+    </ds-truncatable>
+    <div class="d-flex" *ngIf="node===loadingNode && dataSource.loading$ | async"
+         cdkTreeNodePadding>
+      <button type="button" class="btn btn-default invisible">
+        <span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
+              aria-hidden="true"></span>
+      </button>
+      <ds-loading class="ds-loading"></ds-loading>
+    </div>
+  </cdk-tree-node>
+  <!-- This is the tree node template for leaf nodes (collections and (sub)coms without children) -->
+  <cdk-tree-node *cdkTreeNodeDef="let node; when: !(hasChild && isShowMore)" cdkTreeNodePadding
+                 class="example-tree-node childless-node">
+    <div class="btn-group">
+      <button type="button" class="btn btn-default" cdkTreeNodeToggle>
+                <span class="fa fa-chevron-right invisible"
+                      aria-hidden="true"></span>
+      </button>
+      <h6 class="align-middle pt-2">
+        <a [routerLink]="node.route" class="lead">
+          {{node.name}}
+        </a>
+      </h6>
+    </div>
+    <ds-truncatable [id]="node.id">
+      <div class="text-muted" cdkTreeNodePadding>
+        <div class="d-flex" *ngIf="node.payload.shortDescription">
+          <button type="button" class="btn btn-default invisible">
+            <span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
+                  aria-hidden="true"></span>
+          </button>
+          <ds-truncatable-part [id]="node.id" [minLines]="3">
+            <span>{{node.payload.shortDescription}}</span>
+          </ds-truncatable-part>
+        </div>
+      </div>
+    </ds-truncatable>
+  </cdk-tree-node>
+</cdk-tree>
diff --git a/src/app/community-list-page/community-list/community-list.component.spec.ts b/src/app/community-list-page/community-list/community-list.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c04aadda37f9701b81a6e00a3246d26519332f78
--- /dev/null
+++ b/src/app/community-list-page/community-list/community-list.component.spec.ts
@@ -0,0 +1,336 @@
+import { async, ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing';
+
+import { CommunityListComponent } from './community-list.component';
+import {
+  CommunityListService,
+  FlatNode,
+  showMoreFlatNode,
+  toFlatNode
+} from '../community-list-service';
+import { CdkTreeModule } from '@angular/cdk/tree';
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterTestingModule } from '@angular/router/testing';
+import { Community } from '../../core/shared/community.model';
+import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
+import { PaginatedList } from '../../core/data/paginated-list';
+import { PageInfo } from '../../core/shared/page-info.model';
+import { Collection } from '../../core/shared/collection.model';
+import { of as observableOf } from 'rxjs';
+import { By } from '@angular/platform-browser';
+import { isEmpty, isNotEmpty } from '../../shared/empty.util';
+
+describe('CommunityListComponent', () => {
+  let component: CommunityListComponent;
+  let fixture: ComponentFixture<CommunityListComponent>;
+
+  const mockSubcommunities1Page1 = [Object.assign(new Community(), {
+    id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
+    uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
+    name: 'subcommunity1',
+  }),
+    Object.assign(new Community(), {
+      id: '59ee713b-ee53-4220-8c3f-9860dc84fe33',
+      uuid: '59ee713b-ee53-4220-8c3f-9860dc84fe33',
+      name: 'subcommunity2',
+    })
+  ];
+  const mockCollectionsPage1 = [
+    Object.assign(new Collection(), {
+      id: 'e9dbf393-7127-415f-8919-55be34a6e9ed',
+      uuid: 'e9dbf393-7127-415f-8919-55be34a6e9ed',
+      name: 'collection1',
+    }),
+    Object.assign(new Collection(), {
+      id: '59da2ff0-9bf4-45bf-88be-e35abd33f304',
+      uuid: '59da2ff0-9bf4-45bf-88be-e35abd33f304',
+      name: 'collection2',
+    })
+  ];
+  const mockCollectionsPage2 = [
+    Object.assign(new Collection(), {
+      id: 'a5159760-f362-4659-9e81-e3253ad91ede',
+      uuid: 'a5159760-f362-4659-9e81-e3253ad91ede',
+      name: 'collection3',
+    }),
+    Object.assign(new Collection(), {
+      id: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3',
+      uuid: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3',
+      name: 'collection4',
+    })
+  ];
+
+  const mockTopCommunitiesWithChildrenArrays = [
+    {
+      id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+      uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+      subcommunities: mockSubcommunities1Page1,
+      collections: [],
+    },
+    {
+      id: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+      uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+      subcommunities: [],
+      collections: [...mockCollectionsPage1, ...mockCollectionsPage2],
+    },
+    {
+      id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+      uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+      subcommunities: [],
+      collections: [],
+    }];
+
+  const mockTopFlatnodesUnexpanded: FlatNode[] = [
+    toFlatNode(
+      Object.assign(new Community(), {
+        id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+        uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
+        subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)),
+        collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+        name: 'community1',
+      }), observableOf(true), 0, false, null
+    ),
+    toFlatNode(
+      Object.assign(new Community(), {
+        id: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+        uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863',
+        subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+        collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])),
+        name: 'community2',
+      }), observableOf(true), 0, false, null
+    ),
+    toFlatNode(
+      Object.assign(new Community(), {
+        id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+        uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24',
+        subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+        collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+        name: 'community3',
+      }), observableOf(false), 0, false, null
+    ),
+  ];
+  let communityListServiceStub;
+
+  beforeEach(async(() => {
+    communityListServiceStub = {
+      topPageSize: 2,
+      topCurrentPage: 1,
+      collectionPageSize: 2,
+      subcommunityPageSize: 2,
+      expandedNodes: [],
+      loadingNode: null,
+      getNextPageTopCommunities() {
+        this.topCurrentPage++;
+      },
+      getLoadingNodeFromStore() {
+        return observableOf(this.loadingNode);
+      },
+      getExpandedNodesFromStore() {
+        return observableOf(this.expandedNodes);
+      },
+      saveCommunityListStateToStore(expandedNodes, loadingNode) {
+        this.expandedNodes = expandedNodes;
+        this.loadingNode = loadingNode;
+      },
+      loadCommunities(expandedNodes) {
+        let flatnodes;
+        let showMoreTopComNode = false;
+        flatnodes = [...mockTopFlatnodesUnexpanded];
+        const currentPage = this.topCurrentPage;
+        const elementsPerPage = this.topPageSize;
+        let endPageIndex = (currentPage * elementsPerPage);
+        if (endPageIndex >= flatnodes.length) {
+          endPageIndex = flatnodes.length;
+        } else {
+          showMoreTopComNode = true;
+        }
+        if (expandedNodes === null || isEmpty(expandedNodes)) {
+          if (showMoreTopComNode) {
+            return observableOf([...mockTopFlatnodesUnexpanded.slice(0, endPageIndex), showMoreFlatNode('community', 0, null)]);
+          } else {
+            return observableOf(mockTopFlatnodesUnexpanded.slice(0, endPageIndex));
+          }
+        } else {
+          flatnodes = [];
+          const topFlatnodes = mockTopFlatnodesUnexpanded.slice(0, endPageIndex);
+          topFlatnodes.map((topNode: FlatNode) => {
+            flatnodes = [...flatnodes, topNode];
+            const expandedParent: FlatNode = expandedNodes.find((expandedNode: FlatNode) => expandedNode.id === topNode.id);
+            if (isNotEmpty(expandedParent)) {
+              const matchingTopComWithArrays = mockTopCommunitiesWithChildrenArrays.find((topcom) => topcom.id === topNode.id);
+              if (isNotEmpty(matchingTopComWithArrays)) {
+                const possibleSubcoms: Community[] = matchingTopComWithArrays.subcommunities;
+                let subComFlatnodes = [];
+                possibleSubcoms.map((subcom: Community) => {
+                  subComFlatnodes = [...subComFlatnodes, toFlatNode(subcom, observableOf(false), topNode.level + 1, false, topNode)];
+                });
+                const possibleColls: Collection[] = matchingTopComWithArrays.collections;
+                let collFlatnodes = [];
+                possibleColls.map((coll: Collection) => {
+                  collFlatnodes = [...collFlatnodes, toFlatNode(coll, observableOf(false), topNode.level + 1, false, topNode)];
+                });
+                if (isNotEmpty(subComFlatnodes)) {
+                  const endSubComIndex = this.subcommunityPageSize * expandedParent.currentCommunityPage;
+                  flatnodes = [...flatnodes, ...subComFlatnodes.slice(0, endSubComIndex)];
+                  if (subComFlatnodes.length > endSubComIndex) {
+                    flatnodes = [...flatnodes, showMoreFlatNode('community', topNode.level + 1, expandedParent)];
+                  }
+                }
+                if (isNotEmpty(collFlatnodes)) {
+                  const endColIndex = this.collectionPageSize * expandedParent.currentCollectionPage;
+                  flatnodes = [...flatnodes, ...collFlatnodes.slice(0, endColIndex)];
+                  if (collFlatnodes.length > endColIndex) {
+                    flatnodes = [...flatnodes, showMoreFlatNode('collection', topNode.level + 1, expandedParent)];
+                  }
+                }
+              }
+            }
+          });
+          if (showMoreTopComNode) {
+            flatnodes = [...flatnodes, showMoreFlatNode('community', 0, null)];
+          }
+          return observableOf(flatnodes);
+        }
+      }
+    };
+    TestBed.configureTestingModule({
+      imports: [
+        TranslateModule.forRoot({
+          loader: {
+            provide: TranslateLoader,
+            useClass: MockTranslateLoader
+          },
+        }),
+        CdkTreeModule,
+        RouterTestingModule],
+      declarations: [CommunityListComponent],
+      providers: [CommunityListComponent,
+        { provide: CommunityListService, useValue: communityListServiceStub },],
+      schemas: [CUSTOM_ELEMENTS_SCHEMA],
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CommunityListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', inject([CommunityListComponent], (comp: CommunityListComponent) => {
+    expect(comp).toBeTruthy();
+  }));
+
+  it('should render a cdk tree with the first elementsPerPage (2) nr of top level communities, unexpanded', () => {
+    const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a'));
+    const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a'));
+    const allNodes = [...expandableNodesFound, ...childlessNodesFound];
+    expect(allNodes.length).toEqual(2);
+    mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => {
+      expect(allNodes.find((foundEl) => {
+        return (foundEl.nativeElement.textContent.trim() === topFlatnode.name);
+      })).toBeTruthy();
+    });
+  });
+
+  it('show more node is present at end of nodetree', () => {
+    const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node'));
+    expect(showMoreEl.length).toEqual(1);
+    expect(showMoreEl).toBeTruthy();
+  });
+
+  describe('when show more of top communities is clicked', () => {
+    beforeEach(fakeAsync(() => {
+      const showMoreLink = fixture.debugElement.query(By.css('.show-more-node a'));
+      showMoreLink.triggerEventHandler('click', {
+        preventDefault: () => {/**/
+        }
+      });
+      tick();
+      fixture.detectChanges();
+    }));
+    it('tree contains maximum of currentPage (2) * (2) elementsPerPage of first top communities, or less if there are less communities (3)', () => {
+      const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a'));
+      const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a'));
+      const allNodes = [...expandableNodesFound, ...childlessNodesFound];
+      expect(allNodes.length).toEqual(3);
+      mockTopFlatnodesUnexpanded.map((topFlatnode: FlatNode) => {
+        expect(allNodes.find((foundEl) => {
+          return (foundEl.nativeElement.textContent.trim() === topFlatnode.name);
+        })).toBeTruthy();
+      });
+    });
+    it('show more node is gone from end of nodetree', () => {
+      const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node'));
+      expect(showMoreEl.length).toEqual(0);
+    });
+  });
+
+  describe('when first expandable node is expanded', () => {
+    let allNodes;
+    beforeEach(fakeAsync(() => {
+      const chevronExpand = fixture.debugElement.query(By.css('.expandable-node button'));
+      const chevronExpandSpan = fixture.debugElement.query(By.css('.expandable-node button span'));
+      if (chevronExpandSpan.nativeElement.classList.contains('fa-chevron-right')) {
+        chevronExpand.nativeElement.click();
+        tick();
+        fixture.detectChanges();
+      }
+
+      const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a'));
+      const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a'));
+      allNodes = [...expandableNodesFound, ...childlessNodesFound];
+    }));
+    describe('children of first expandable node are added to tree (page-limited)', () => {
+      it('tree contains page-limited topcoms (2) and children of first expandable node (2subcoms)', () => {
+        expect(allNodes.length).toEqual(4);
+        mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => {
+          expect(allNodes.find((foundEl) => {
+            return (foundEl.nativeElement.textContent.trim() === topFlatnode.name);
+          })).toBeTruthy();
+        });
+        mockSubcommunities1Page1.map((subcom) => {
+          expect(allNodes.find((foundEl) => {
+            return (foundEl.nativeElement.textContent.trim() === subcom.name);
+          })).toBeTruthy();
+        })
+      });
+    });
+  });
+
+  describe('second top community node is expanded and has more children (collections) than page size of collection', () => {
+    describe('children of second top com are added (page-limited pageSize 2)', () => {
+      let allNodes;
+      beforeEach(fakeAsync(() => {
+        const chevronExpand = fixture.debugElement.queryAll(By.css('.expandable-node button'));
+        const chevronExpandSpan = fixture.debugElement.queryAll(By.css('.expandable-node button span'));
+        if (chevronExpandSpan[1].nativeElement.classList.contains('fa-chevron-right')) {
+          chevronExpand[1].nativeElement.click();
+          tick();
+          fixture.detectChanges();
+        }
+
+        const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a'));
+        const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a'));
+        allNodes = [...expandableNodesFound, ...childlessNodesFound];
+      }));
+      it('tree contains 2 (page-limited) top com, 2 (page-limited) coll of 2nd top com, a show more for those page-limited coll and show more for page-limited top com', () => {
+        mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => {
+          expect(allNodes.find((foundEl) => {
+            return (foundEl.nativeElement.textContent.trim() === topFlatnode.name);
+          })).toBeTruthy();
+        });
+        mockCollectionsPage1.map((coll) => {
+          expect(allNodes.find((foundEl) => {
+            return (foundEl.nativeElement.textContent.trim() === coll.name);
+          })).toBeTruthy();
+        });
+        expect(allNodes.length).toEqual(4);
+        const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node'));
+        expect(showMoreEl.length).toEqual(2);
+      });
+    });
+  });
+
+});
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ddcd49cd1cc470f16f3565fa03dea853874adff0
--- /dev/null
+++ b/src/app/community-list-page/community-list/community-list.component.ts
@@ -0,0 +1,104 @@
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { take } from 'rxjs/operators';
+import { CommunityListService, FlatNode } from '../community-list-service';
+import { CommunityListDatasource } from '../community-list-datasource';
+import { FlatTreeControl } from '@angular/cdk/tree';
+import { isEmpty } from '../../shared/empty.util';
+
+/**
+ * A tree-structured list of nodes representing the communities, their subCommunities and collections.
+ * Initially only the page-restricted top communities are shown.
+ * Each node can be expanded to show its children and all children are also page-limited.
+ * More pages of a page-limited result can be shown by pressing a show more node/link.
+ * Which nodes were expanded is kept in the store, so this persists across pages.
+ */
+@Component({
+  selector: 'ds-community-list',
+  templateUrl: './community-list.component.html',
+})
+export class CommunityListComponent implements OnInit, OnDestroy {
+
+  private expandedNodes: FlatNode[] = [];
+  public loadingNode: FlatNode;
+
+  treeControl = new FlatTreeControl<FlatNode>(
+    (node) => node.level, (node) => true
+  );
+
+  dataSource: CommunityListDatasource;
+
+  constructor(private communityListService: CommunityListService) {
+  }
+
+  ngOnInit() {
+    this.dataSource = new CommunityListDatasource(this.communityListService);
+    this.communityListService.getLoadingNodeFromStore().pipe(take(1)).subscribe((result) => {
+      this.loadingNode = result;
+    });
+    this.communityListService.getExpandedNodesFromStore().pipe(take(1)).subscribe((result) => {
+      this.expandedNodes = [...result];
+      this.dataSource.loadCommunities(this.expandedNodes);
+    });
+  }
+
+  ngOnDestroy(): void {
+    this.communityListService.saveCommunityListStateToStore(this.expandedNodes, this.loadingNode);
+  }
+
+  // whether or not this node has children (subcommunities or collections)
+  hasChild(_: number, node: FlatNode) {
+    return node.isExpandable$;
+  }
+
+  // whether or not it is a show more node (contains no data, but is indication that there are more topcoms, subcoms or collections
+  isShowMore(_: number, node: FlatNode) {
+    return node.isShowMoreNode;
+  }
+
+  /**
+   * Toggles the expanded variable of a node, adds it to the exapanded nodes list and reloads the tree so this node is expanded
+   * @param node  Node we want to expand
+   */
+  toggleExpanded(node: FlatNode) {
+    this.loadingNode = node;
+    if (node.isExpanded) {
+      this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name);
+      node.isExpanded = false;
+    } else {
+      this.expandedNodes.push(node);
+      node.isExpanded = true;
+      if (isEmpty(node.currentCollectionPage)) {
+        node.currentCollectionPage = 1;
+      }
+      if (isEmpty(node.currentCommunityPage)) {
+        node.currentCommunityPage = 1;
+      }
+    }
+    this.dataSource.loadCommunities(this.expandedNodes);
+  }
+
+  /**
+   * Makes sure the next page of a node is added to the tree (top community, sub community of collection)
+   *      > Finds its parent (if not top community) and increases its corresponding collection/subcommunity currentPage
+   *      > Reloads tree with new page added to corresponding top community lis, sub community list or collection list
+   * @param node  The show more node indicating whether it's an increase in top communities, sub communities or collections
+   */
+  getNextPage(node: FlatNode): void {
+    this.loadingNode = node;
+    if (node.parent != null) {
+      if (node.id === 'collection') {
+        const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id);
+        parentNodeInExpandedNodes.currentCollectionPage++;
+      }
+      if (node.id === 'community') {
+        const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id);
+        parentNodeInExpandedNodes.currentCommunityPage++;
+      }
+      this.dataSource.loadCommunities(this.expandedNodes);
+    } else {
+      this.communityListService.getNextPageTopCommunities();
+      this.dataSource.loadCommunities(this.expandedNodes);
+    }
+  }
+
+}
diff --git a/src/app/core/cache/models/normalized-item.model.ts b/src/app/core/cache/models/normalized-item.model.ts
index c613c59a0c629fa0c38c670c2210f5c07b5b28d5..9b7edf70c060c7767139ac254c375f318e80cb54 100644
--- a/src/app/core/cache/models/normalized-item.model.ts
+++ b/src/app/core/cache/models/normalized-item.model.ts
@@ -65,7 +65,7 @@ export class NormalizedItem extends NormalizedDSpaceObject<Item> {
   @relationship(Bundle, true)
   bundles: string[];
 
-  @autoserialize
+  @deserialize
   @relationship(Relationship, true)
   relationships: string[];
 
diff --git a/src/app/core/cache/models/search-param.model.ts b/src/app/core/cache/models/search-param.model.ts
index a33bbee5e6a6ffdec959cd7bc97507548588058b..3881dbe8b7b25d14ed9bd070d1efe11f5c9fc057 100644
--- a/src/app/core/cache/models/search-param.model.ts
+++ b/src/app/core/cache/models/search-param.model.ts
@@ -1,6 +1,6 @@
 
 /**
- * Class representing a query parameter (query?fieldName=fieldValue) used in FindAllOptions object
+ * Class representing a query parameter (query?fieldName=fieldValue) used in FindListOptions object
  */
 export class SearchParam {
   constructor(public fieldName: string, public fieldValue: any) {
diff --git a/src/app/core/config/config.service.spec.ts b/src/app/core/config/config.service.spec.ts
index 87add6b656a7708037f70536f12fa1835ca17bca..402ee88b81e381d833bd385f7d85dde91c20fce2 100644
--- a/src/app/core/config/config.service.spec.ts
+++ b/src/app/core/config/config.service.spec.ts
@@ -3,7 +3,7 @@ import { TestScheduler } from 'rxjs/testing';
 import { getMockRequestService } from '../../shared/mocks/mock-request.service';
 import { ConfigService } from './config.service';
 import { RequestService } from '../data/request.service';
-import { ConfigRequest, FindAllOptions } from '../data/request.models';
+import { ConfigRequest, FindListOptions } from '../data/request.models';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
 
@@ -27,7 +27,7 @@ describe('ConfigService', () => {
   let requestService: RequestService;
   let halService: any;
 
-  const findOptions: FindAllOptions = new FindAllOptions();
+  const findOptions: FindListOptions = new FindListOptions();
 
   const scopeName = 'traditional';
   const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
diff --git a/src/app/core/config/config.service.ts b/src/app/core/config/config.service.ts
index 340a7a97d6d9a7c481e0ac3d130936a9764fcdd5..db14c4a256b06de61da8e3965b480e6f635f9722 100644
--- a/src/app/core/config/config.service.ts
+++ b/src/app/core/config/config.service.ts
@@ -2,7 +2,7 @@ import { merge as observableMerge, Observable, throwError as observableThrowErro
 import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
 import { RequestService } from '../data/request.service';
 import { ConfigSuccessResponse } from '../cache/response.models';
-import { ConfigRequest, FindAllOptions, RestRequest } from '../data/request.models';
+import { ConfigRequest, FindListOptions, RestRequest } from '../data/request.models';
 import { hasValue, isNotEmpty } from '../../shared/empty.util';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { ConfigData } from './config-data';
@@ -35,7 +35,7 @@ export abstract class ConfigService {
     return `${endpoint}/${resourceName}`;
   }
 
-  protected getConfigSearchHref(endpoint, options: FindAllOptions = {}): string {
+  protected getConfigSearchHref(endpoint, options: FindListOptions = {}): string {
     let result;
     const args = [];
 
@@ -93,7 +93,7 @@ export abstract class ConfigService {
       distinctUntilChanged());
   }
 
-  public getConfigBySearch(options: FindAllOptions = {}): Observable<ConfigData> {
+  public getConfigBySearch(options: FindListOptions = {}): Observable<ConfigData> {
     return this.halService.getEndpoint(this.linkPath).pipe(
       map((endpoint: string) => this.getConfigSearchHref(endpoint, options)),
       filter((href: string) => isNotEmpty(href)),
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index dedf6104d9b97e02f476ec7f3c1b20aea34e21bb..4fdef02357e396383a4dc53b672cb00091350e19 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -128,8 +128,8 @@ import {
   MOCK_RESPONSE_MAP,
   MockResponseMap,
   mockResponseMap
-} from './dspace-rest-v2/mocks/mock-response-map';
-import { EndpointMockingRestService } from './dspace-rest-v2/endpoint-mocking-rest.service';
+} from '../shared/mocks/dspace-rest-v2/mocks/mock-response-map';
+import { EndpointMockingRestService } from '../shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service';
 import { ENV_CONFIG, GLOBAL_CONFIG, GlobalConfig } from '../../config';
 import { SearchFilterService } from './shared/search/search-filter.service';
 import { SearchConfigurationService } from './shared/search/search-configuration.service';
@@ -137,6 +137,10 @@ import { SelectableListService } from '../shared/object-list/selectable-list/sel
 import { RelationshipTypeService } from './data/relationship-type.service';
 import { SidebarService } from '../shared/sidebar/sidebar.service';
 
+/**
+ * When not in production, endpoint responses can be mocked for testing purposes
+ * If there is no mock version available for the endpoint, the actual REST response will be used just like in production mode
+ */
 export const restServiceFactory = (cfg: GlobalConfig, mocks: MockResponseMap, http: HttpClient) => {
   if (ENV_CONFIG.production) {
     return new DSpaceRESTv2Service(http);
diff --git a/src/app/core/data/bitstream-format-data.service.ts b/src/app/core/data/bitstream-format-data.service.ts
index bdf9b16acfdf23703b6fdba715f4fb9967a89243..7255ed366339189803676296c3a9e291e4581cc4 100644
--- a/src/app/core/data/bitstream-format-data.service.ts
+++ b/src/app/core/data/bitstream-format-data.service.ts
@@ -11,7 +11,7 @@ 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 { DeleteByIDRequest, FindListOptions, PostRequest, PutRequest } from './request.models';
 import { Observable } from 'rxjs';
 import { find, map, tap } from 'rxjs/operators';
 import { configureRequest, getResponseFromEntry } from '../shared/operators';
@@ -54,10 +54,10 @@ export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
 
   /**
    * Get the endpoint for browsing bitstream formats
-   * @param {FindAllOptions} options
+   * @param {FindListOptions} options
    * @returns {Observable<string>}
    */
-  getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable<string> {
+  getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> {
     return this.halService.getEndpoint(this.linkPath);
   }
 
diff --git a/src/app/core/data/bundle-data.service.ts b/src/app/core/data/bundle-data.service.ts
index 5962488c4f44c3db0045b10e1bb97d825819fd55..280f727aadc7d93c484bc9594cd6397e4eacbbdc 100644
--- a/src/app/core/data/bundle-data.service.ts
+++ b/src/app/core/data/bundle-data.service.ts
@@ -11,7 +11,7 @@ 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 { FindAllOptions } from './request.models';
+import { FindListOptions } from './request.models';
 import { Observable } from 'rxjs/internal/Observable';
 
 /**
@@ -37,10 +37,10 @@ export class BundleDataService extends DataService<Bundle> {
 
   /**
    * Get the endpoint for browsing bundles
-   * @param {FindAllOptions} options
+   * @param {FindListOptions} options
    * @returns {Observable<string>}
    */
-  getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable<string> {
+  getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> {
     return this.halService.getEndpoint(this.linkPath);
   }
 }
diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts
index 9e4962ee714e5a479551924b9926d447017b828a..0c032e676655ad69ae2ab7a4eddc0471270cdf0b 100644
--- a/src/app/core/data/collection-data.service.ts
+++ b/src/app/core/data/collection-data.service.ts
@@ -1,6 +1,6 @@
 import { Injectable } from '@angular/core';
 
-import { distinctUntilChanged, filter, map, take } from 'rxjs/operators';
+import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators';
 import { Store } from '@ngrx/store';
 
 import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -16,7 +16,7 @@ import { HttpClient } from '@angular/common/http';
 import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
 import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
 import { Observable } from 'rxjs/internal/Observable';
-import { FindAllOptions, GetRequest } from './request.models';
+import {FindListOptions, FindListRequest, GetRequest} from './request.models';
 import { RemoteData } from './remote-data';
 import { PaginatedList } from './paginated-list';
 import { configureRequest } from '../shared/operators';
@@ -50,11 +50,11 @@ export class CollectionDataService extends ComColDataService<Collection> {
   /**
    * Get all collections the user is authorized to submit to
    *
-   * @param options The [[FindAllOptions]] object
+   * @param options The [[FindListOptions]] object
    * @return Observable<RemoteData<PaginatedList<Collection>>>
    *    collection list
    */
-  getAuthorizedCollection(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
+  getAuthorizedCollection(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
     const searchHref = 'findAuthorized';
 
     return this.searchBy(searchHref, options).pipe(
@@ -65,11 +65,11 @@ export class CollectionDataService extends ComColDataService<Collection> {
    * Get all collections the user is authorized to submit to, by community
    *
    * @param communityId The community id
-   * @param options The [[FindAllOptions]] object
+   * @param options The [[FindListOptions]] object
    * @return Observable<RemoteData<PaginatedList<Collection>>>
    *    collection list
    */
-  getAuthorizedCollectionByCommunity(communityId: string, options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
+  getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
     const searchHref = 'findAuthorizedByCommunity';
     options = Object.assign({}, options, {
       searchParams: [new SearchParam('uuid', communityId)]
@@ -87,7 +87,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
    */
   hasAuthorizedCollection(): Observable<boolean> {
     const searchHref = 'findAuthorized';
-    const options = new FindAllOptions();
+    const options = new FindListOptions();
     options.elementsPerPage = 1;
 
     return this.searchBy(searchHref, options).pipe(
@@ -138,4 +138,10 @@ export class CollectionDataService extends ComColDataService<Collection> {
     return this.rdbService.buildList(href$);
   }
 
+  protected getFindByParentHref(parentUUID: string): Observable<string> {
+    return this.halService.getEndpoint('communities').pipe(
+      switchMap((communityEndpointHref: string) =>
+        this.halService.getEndpoint('collections', `${communityEndpointHref}/${parentUUID}`)),
+    );
+  }
 }
diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts
index 5cc474dff9fbdfadb1d111e5fff918339cc2773c..a7fcd205d4ace43ddbbbb61f80e21dc4f6addd48 100644
--- a/src/app/core/data/comcol-data.service.spec.ts
+++ b/src/app/core/data/comcol-data.service.spec.ts
@@ -8,12 +8,12 @@ import { ObjectCacheService } from '../cache/object-cache.service';
 import { CoreState } from '../core.reducers';
 import { ComColDataService } from './comcol-data.service';
 import { CommunityDataService } from './community-data.service';
-import { FindAllOptions, FindByIDRequest } from './request.models';
+import { FindListOptions, FindByIDRequest } from './request.models';
 import { RequestService } from './request.service';
 import { NormalizedObject } from '../cache/models/normalized-object.model';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { RequestEntry } from './request.reducer';
-import { of as observableOf } from 'rxjs';
+import {Observable, of as observableOf} from 'rxjs';
 import { NotificationsService } from '../../shared/notifications/notifications.service';
 import { HttpClient } from '@angular/common/http';
 import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
@@ -45,6 +45,11 @@ class TestService extends ComColDataService<any> {
   ) {
     super();
   }
+
+  protected getFindByParentHref(parentUUID: string): Observable<string> {
+    // implementation in subclasses for communities/collections
+    return undefined;
+  }
 }
 
 /* tslint:enable:max-classes-per-file */
@@ -66,7 +71,7 @@ describe('ComColDataService', () => {
   const dataBuildService = {} as NormalizedObjectBuildService;
 
   const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
-  const options = Object.assign(new FindAllOptions(), {
+  const options = Object.assign(new FindListOptions(), {
     scopeID: scopeID
   });
   const getRequestEntry$ = (successful: boolean) => {
diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts
index 68eb3e488065b8852a7d607e2e5ea6acf8cea5c1..867ee24fc102f584dc2af65a3f70e7a6ef4a0acd 100644
--- a/src/app/core/data/comcol-data.service.ts
+++ b/src/app/core/data/comcol-data.service.ts
@@ -1,12 +1,23 @@
-import { distinctUntilChanged, filter, map, mergeMap, share, take, tap } from 'rxjs/operators';
+import {
+  distinctUntilChanged,
+  filter, first,
+  map,
+  mergeMap,
+  share,
+  switchMap,
+  take,
+  tap
+} from 'rxjs/operators';
 import { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs';
-import { isEmpty, isNotEmpty } from '../../shared/empty.util';
+import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
 import { NormalizedCommunity } from '../cache/models/normalized-community.model';
 import { ObjectCacheService } from '../cache/object-cache.service';
 import { CommunityDataService } from './community-data.service';
 
 import { DataService } from './data.service';
-import { FindAllOptions, FindByIDRequest } from './request.models';
+import { PaginatedList } from './paginated-list';
+import { RemoteData } from './remote-data';
+import { FindListOptions, FindByIDRequest } from './request.models';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { getResponseFromEntry } from '../shared/operators';
 import { CacheableObject } from '../cache/object-cache.reducer';
@@ -26,7 +37,7 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
    * @return { Observable<string> }
    *    an Observable<string> containing the scoped URL
    */
-  public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
+  public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
     if (isEmpty(options.scopeID)) {
       return this.halService.getEndpoint(linkPath);
     } else {
@@ -57,4 +68,12 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
       return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged(), share());
     }
   }
+
+  protected abstract getFindByParentHref(parentUUID: string): Observable<string>;
+
+  public findByParent(parentUUID: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
+    const href$ = this.buildHrefFromFindOptions(this.getFindByParentHref(parentUUID), [], options);
+    return this.findList(href$, options);
+  }
+
 }
diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts
index cc55fe68693938f808ae8cb8d84489a6955102dc..57bf64678f727427e51edf94311a5fec79e2b147 100644
--- a/src/app/core/data/community-data.service.ts
+++ b/src/app/core/data/community-data.service.ts
@@ -1,4 +1,4 @@
-import { filter, take } from 'rxjs/operators';
+import { filter, switchMap, take } from 'rxjs/operators';
 import { Injectable } from '@angular/core';
 
 import { Store } from '@ngrx/store';
@@ -9,7 +9,7 @@ import { Community } from '../shared/community.model';
 import { ComColDataService } from './comcol-data.service';
 import { RequestService } from './request.service';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
-import { FindAllOptions, FindAllRequest } from './request.models';
+import { FindListOptions, FindListRequest } from './request.models';
 import { RemoteData } from './remote-data';
 import { hasValue } from '../../shared/empty.util';
 import { Observable } from 'rxjs';
@@ -43,16 +43,24 @@ export class CommunityDataService extends ComColDataService<Community> {
     return this.halService.getEndpoint(this.linkPath);
   }
 
-  findTop(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<Community>>> {
+  findTop(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Community>>> {
     const hrefObs = this.getFindAllHref(options, this.topLinkPath);
     hrefObs.pipe(
       filter((href: string) => hasValue(href)),
       take(1))
       .subscribe((href: string) => {
-        const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
+        const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
         this.requestService.configure(request);
       });
 
     return this.rdbService.buildList<Community>(hrefObs) as Observable<RemoteData<PaginatedList<Community>>>;
   }
+
+  protected getFindByParentHref(parentUUID: string): Observable<string> {
+    return this.halService.getEndpoint(this.linkPath).pipe(
+      switchMap((communityEndpointHref: string) =>
+        this.halService.getEndpoint('subcommunities', `${communityEndpointHref}/${parentUUID}`))
+    );
+  }
+
 }
diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts
index b690492c61e3b697249095b8be68b90cbcc07ae9..98e5f7afaa17f2304beef43d2021200511abe47f 100644
--- a/src/app/core/data/data.service.spec.ts
+++ b/src/app/core/data/data.service.spec.ts
@@ -6,7 +6,7 @@ import { CoreState } from '../core.reducers';
 import { Store } from '@ngrx/store';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { Observable, of as observableOf } from 'rxjs';
-import { FindAllOptions } from './request.models';
+import { FindListOptions } from './request.models';
 import { SortDirection, SortOptions } from '../cache/models/sort-options.model';
 import { ObjectCacheService } from '../cache/object-cache.service';
 import { compare, Operation } from 'fast-json-patch';
@@ -42,7 +42,7 @@ class TestService extends DataService<any> {
     super();
   }
 
-  public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
+  public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
     return observableOf(endpoint);
   }
 }
@@ -56,7 +56,7 @@ class DummyChangeAnalyzer implements ChangeAnalyzer<NormalizedTestObject> {
 
 describe('DataService', () => {
   let service: TestService;
-  let options: FindAllOptions;
+  let options: FindListOptions;
   const requestService = {generateRequestId: () => uuidv4()} as RequestService;
   const halService = {} as HALEndpointService;
   const rdbService = {} as RemoteDataBuildService;
diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts
index ddf2c3a1d5c79061e152ba647e4490a37b63eb30..ce9a01a569eb1b079e1e860930710277fad859b9 100644
--- a/src/app/core/data/data.service.ts
+++ b/src/app/core/data/data.service.ts
@@ -11,7 +11,14 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { URLCombiner } from '../url-combiner/url-combiner';
 import { PaginatedList } from './paginated-list';
 import { RemoteData } from './remote-data';
-import { CreateRequest, DeleteByIDRequest, FindAllOptions, FindAllRequest, FindByIDRequest, GetRequest } from './request.models';
+import {
+  CreateRequest,
+  DeleteByIDRequest,
+  FindListOptions,
+  FindListRequest,
+  FindByIDRequest,
+  GetRequest
+} from './request.models';
 import { RequestService } from './request.service';
 import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
 import { NormalizedObject } from '../cache/models/normalized-object.model';
@@ -47,17 +54,17 @@ export abstract class DataService<T extends CacheableObject> {
    */
   protected responseMsToLive: number;
 
-  public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable<string>
+  public abstract getBrowseEndpoint(options: FindListOptions, linkPath?: string): Observable<string>
 
   /**
    * Create the HREF with given options object
    *
-   * @param options The [[FindAllOptions]] object
+   * @param options The [[FindListOptions]] object
    * @param linkPath The link path for the object
    * @return {Observable<string>}
    *    Return an observable that emits created HREF
    */
-  protected getFindAllHref(options: FindAllOptions = {}, linkPath?: string): Observable<string> {
+  protected getFindAllHref(options: FindListOptions = {}, linkPath?: string): Observable<string> {
     let result: Observable<string>;
     const args = [];
 
@@ -70,11 +77,11 @@ export abstract class DataService<T extends CacheableObject> {
    * Create the HREF for a specific object's search method with given options object
    *
    * @param searchMethod The search method for the object
-   * @param options The [[FindAllOptions]] object
+   * @param options The [[FindListOptions]] object
    * @return {Observable<string>}
    *    Return an observable that emits created HREF
    */
-  protected getSearchByHref(searchMethod: string, options: FindAllOptions = {}): Observable<string> {
+  protected getSearchByHref(searchMethod: string, options: FindListOptions = {}): Observable<string> {
     let result: Observable<string>;
     const args = [];
 
@@ -94,11 +101,11 @@ export abstract class DataService<T extends CacheableObject> {
    *
    * @param href$ The HREF to which the query string should be appended
    * @param args Array with additional params to combine with query string
-   * @param options The [[FindAllOptions]] object
+   * @param options The [[FindListOptions]] object
    * @return {Observable<string>}
    *    Return an observable that emits created HREF
    */
-  protected buildHrefFromFindOptions(href$: Observable<string>, args: string[], options: FindAllOptions): Observable<string> {
+  protected buildHrefFromFindOptions(href$: Observable<string>, args: string[], options: FindListOptions): Observable<string> {
 
     if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
       /* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
@@ -120,20 +127,22 @@ export abstract class DataService<T extends CacheableObject> {
     }
   }
 
-  findAll(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
-    const hrefObs = this.getFindAllHref(options);
+  findAll(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
+    return this.findList(this.getFindAllHref(options), options);
+  }
 
-    hrefObs.pipe(
+  protected findList(href$, options: FindListOptions) {
+    href$.pipe(
       first((href: string) => hasValue(href)))
       .subscribe((href: string) => {
-        const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
+        const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
         if (hasValue(this.responseMsToLive)) {
           request.responseMsToLive = this.responseMsToLive;
         }
         this.requestService.configure(request);
       });
 
-    return this.rdbService.buildList<T>(hrefObs) as Observable<RemoteData<PaginatedList<T>>>;
+    return this.rdbService.buildList<T>(href$) as Observable<RemoteData<PaginatedList<T>>>;
   }
 
   /**
@@ -148,7 +157,7 @@ export abstract class DataService<T extends CacheableObject> {
   findById(id: string): Observable<RemoteData<T>> {
 
     const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
-        map((endpoint: string) => this.getIDHref(endpoint, encodeURIComponent(id))));
+      map((endpoint: string) => this.getIDHref(endpoint, encodeURIComponent(id))));
 
     hrefObs.pipe(
       find((href: string) => hasValue(href)))
@@ -184,14 +193,14 @@ export abstract class DataService<T extends CacheableObject> {
   }
 
   /**
-   * Make a new FindAllRequest with given search method
+   * Make a new FindListRequest with given search method
    *
    * @param searchMethod The search method for the object
-   * @param options The [[FindAllOptions]] object
+   * @param options The [[FindListOptions]] object
    * @return {Observable<RemoteData<PaginatedList<T>>}
    *    Return an observable that emits response from the server
    */
-  protected searchBy(searchMethod: string, options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
+  protected searchBy(searchMethod: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
 
     const hrefObs = this.getSearchByHref(searchMethod, options);
 
@@ -199,7 +208,7 @@ export abstract class DataService<T extends CacheableObject> {
       find((href: string) => hasValue(href)),
       tap((href: string) => {
           this.requestService.removeByHrefSubstring(href);
-          const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
+          const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
           request.responseMsToLive = 10 * 1000;
 
           this.requestService.configure(request);
diff --git a/src/app/core/data/dso-redirect-data.service.ts b/src/app/core/data/dso-redirect-data.service.ts
index 7e71f82bbf0e06c778998ec09ecc2485d711737c..f4999637b344e45c74b1cb3de838fa9080394408 100644
--- a/src/app/core/data/dso-redirect-data.service.ts
+++ b/src/app/core/data/dso-redirect-data.service.ts
@@ -8,7 +8,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
 import { RequestService } from './request.service';
 import { Store } from '@ngrx/store';
 import { CoreState } from '../core.reducers';
-import { FindAllOptions, FindByIDRequest, IdentifierType } from './request.models';
+import { FindListOptions, FindByIDRequest, IdentifierType } from './request.models';
 import { Observable } from 'rxjs';
 import { RemoteData } from './remote-data';
 import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
@@ -40,7 +40,7 @@ export class DsoRedirectDataService extends DataService<any> {
     super();
   }
 
-  getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
+  getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
     return this.halService.getEndpoint(linkPath);
   }
 
diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts
index bb02afbcd179d3a60380c99083134a02d59763bb..002ac3cdbc90835e791b1e2924d213825b94a1ba 100644
--- a/src/app/core/data/dspace-object-data.service.ts
+++ b/src/app/core/data/dspace-object-data.service.ts
@@ -8,7 +8,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { DataService } from './data.service';
 import { RemoteData } from './remote-data';
 import { RequestService } from './request.service';
-import { FindAllOptions } from './request.models';
+import { FindListOptions } from './request.models';
 import { ObjectCacheService } from '../cache/object-cache.service';
 import { NotificationsService } from '../../shared/notifications/notifications.service';
 import { HttpClient } from '@angular/common/http';
@@ -32,7 +32,7 @@ class DataServiceImpl extends DataService<DSpaceObject> {
     super();
   }
 
-  getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
+  getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
     return this.halService.getEndpoint(linkPath);
   }
 
diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts
index 36b8e6b3c5925583fdc18e83fdb13bec662deb0f..44c5f48cfe2d703098d45f98616015de263e234d 100644
--- a/src/app/core/data/item-data.service.spec.ts
+++ b/src/app/core/data/item-data.service.spec.ts
@@ -2,14 +2,13 @@ import { Store } from '@ngrx/store';
 import { cold, getTestScheduler } from 'jasmine-marbles';
 import { TestScheduler } from 'rxjs/testing';
 import { BrowseService } from '../browse/browse.service';
-import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
 import { CoreState } from '../core.reducers';
 import { ItemDataService } from './item-data.service';
 import { RequestService } from './request.service';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
 import {
   DeleteRequest,
-  FindAllOptions,
+  FindListOptions,
   GetRequest,
   MappedCollectionsRequest,
   PostRequest,
@@ -58,7 +57,7 @@ describe('ItemDataService', () => {
   } as HALEndpointService;
 
   const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39';
-  const options = Object.assign(new FindAllOptions(), {
+  const options = Object.assign(new FindListOptions(), {
     scopeID: scopeID,
     sort: {
       field: '',
diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts
index e616cb8020a348da5c83dc0675c1a5e2f3646fee..b729c0fafe0eada6f7713282cb0856038a218702 100644
--- a/src/app/core/data/item-data.service.ts
+++ b/src/app/core/data/item-data.service.ts
@@ -14,7 +14,7 @@ import { RequestService } from './request.service';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
 import {
   DeleteRequest,
-  FindAllOptions,
+  FindListOptions,
   MappedCollectionsRequest,
   PatchRequest,
   PostRequest, PutRequest,
@@ -59,10 +59,10 @@ export class ItemDataService extends DataService<Item> {
   /**
    * Get the endpoint for browsing items
    *  (When options.sort.field is empty, the default field to browse by will be 'dc.date.issued')
-   * @param {FindAllOptions} options
+   * @param {FindListOptions} options
    * @returns {Observable<string>}
    */
-  public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
+  public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
     let field = 'dc.date.issued';
     if (options.sort && options.sort.field) {
       field = options.sort.field;
@@ -247,4 +247,14 @@ export class ItemDataService extends DataService<Item> {
       map((request: RequestEntry) => request.response)
     );
   }
+
+  /**
+   * Get the endpoint for an item's bitstreams
+   * @param itemId
+   */
+  public getBitstreamsEndpoint(itemId: string): Observable<string> {
+    return this.halService.getEndpoint(this.linkPath).pipe(
+      switchMap((url: string) => this.halService.getEndpoint('bitstreams', `${url}/${itemId}`))
+    );
+  }
 }
diff --git a/src/app/core/data/metadata-schema-data.service.ts b/src/app/core/data/metadata-schema-data.service.ts
index b15dd6865f6b604baa3758201f9cedcc99214804..662eaa6c7c31e23f04c5bc4a798b36a20bd78dd5 100644
--- a/src/app/core/data/metadata-schema-data.service.ts
+++ b/src/app/core/data/metadata-schema-data.service.ts
@@ -7,7 +7,7 @@ import { CoreState } from '../core.reducers';
 import { DataService } from './data.service';
 import { RequestService } from './request.service';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
-import { FindAllOptions } from './request.models';
+import { FindListOptions } from './request.models';
 import { ObjectCacheService } from '../cache/object-cache.service';
 import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
 import { HttpClient } from '@angular/common/http';
@@ -33,7 +33,7 @@ class DataServiceImpl extends DataService<MetadataSchema> {
     super();
   }
 
-  getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
+  getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
     return this.halService.getEndpoint(linkPath);
   }
 }
diff --git a/src/app/core/data/relationship-type.service.ts b/src/app/core/data/relationship-type.service.ts
index a30dd5d57e88faf44eb4bdf33c25d7842456679a..627fc4863f053cc14238b47bf127a5aa0f10f587 100644
--- a/src/app/core/data/relationship-type.service.ts
+++ b/src/app/core/data/relationship-type.service.ts
@@ -4,7 +4,6 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
 import { filter, find, map, switchMap, tap } from 'rxjs/operators';
 import { configureRequest, getSucceededRemoteData } from '../shared/operators';
-import { FindAllOptions, FindAllRequest } from './request.models';
 import { Observable } from 'rxjs/internal/Observable';
 import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
 import { RemoteData } from './remote-data';
@@ -12,6 +11,7 @@ import { PaginatedList } from './paginated-list';
 import { combineLatest as observableCombineLatest } from 'rxjs';
 import { ItemType } from '../shared/item-relationships/item-type.model';
 import { isNotUndefined } from '../../shared/empty.util';
+import { FindListOptions, FindListRequest } from './request.models';
 
 /**
  * The service handling all relationship requests
@@ -35,11 +35,11 @@ export class RelationshipTypeService {
     );
   }
 
-  getAllRelationshipTypes(options: FindAllOptions): Observable<RemoteData<PaginatedList<RelationshipType>>> {
+  getAllRelationshipTypes(options: FindListOptions): Observable<RemoteData<PaginatedList<RelationshipType>>> {
     const link$ = this.halService.getEndpoint(this.linkPath);
     return link$
       .pipe(
-        map((endpointURL: string) => new FindAllRequest(this.requestService.generateRequestId(), endpointURL, options)),
+        map((endpointURL: string) => new FindListRequest(this.requestService.generateRequestId(), endpointURL, options)),
         configureRequest(this.requestService),
         switchMap(() => this.rdbService.buildList(link$))
       );
diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts
index 9bd59ce15114aea817967c3ace7b412b40463a76..e155b1f90b12a906bf527d265a3d348a520123ad 100644
--- a/src/app/core/data/relationship.service.ts
+++ b/src/app/core/data/relationship.service.ts
@@ -3,39 +3,20 @@ import { Injectable } from '@angular/core';
 import { MemoizedSelector, select, Store } from '@ngrx/store';
 import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
 import { Observable } from 'rxjs/internal/Observable';
-import {
-  distinctUntilChanged,
-  filter,
-  map,
-  mergeMap,
-  startWith,
-  switchMap,
-  take,
-  tap
-} from 'rxjs/operators';
-import {
-  compareArraysUsingIds,
-  paginatedRelationsToItems,
-  relationsToItems
-} from '../../+item-page/simple/item-types/shared/item-relationships-utils';
+import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
+import { compareArraysUsingIds, paginatedRelationsToItems, relationsToItems } from '../../+item-page/simple/item-types/shared/item-relationships-utils';
 import { AppState, keySelector } from '../../app.reducer';
-import {
-  hasValue,
-  hasValueOperator,
-  isNotEmpty,
-  isNotEmptyOperator
-} from '../../shared/empty.util';
+import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
 import { ReorderableRelationship } from '../../shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
-import {
-  RemoveNameVariantAction,
-  SetNameVariantAction
-} from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
+import { RemoveNameVariantAction, SetNameVariantAction } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
 import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
 import { NotificationsService } from '../../shared/notifications/notifications.service';
 import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
 import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
 import { SearchParam } from '../cache/models/search-param.model';
 import { ObjectCacheService } from '../cache/object-cache.service';
+import { configureRequest, getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators';
+import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
 import { RestResponse } from '../cache/response.models';
 import { CoreState } from '../core.reducers';
 import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
@@ -43,18 +24,11 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
 import { Relationship } from '../shared/item-relationships/relationship.model';
 import { Item } from '../shared/item.model';
-import {
-  configureRequest,
-  getRemoteDataPayload,
-  getResponseFromEntry,
-  getSucceededRemoteData
-} from '../shared/operators';
 import { DataService } from './data.service';
 import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
 import { ItemDataService } from './item-data.service';
 import { PaginatedList } from './paginated-list';
 import { RemoteData, RemoteDataState } from './remote-data';
-import { DeleteRequest, FindAllOptions, PostRequest, RestRequest } from './request.models';
 import { RequestService } from './request.service';
 
 const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
@@ -89,7 +63,7 @@ export class RelationshipService extends DataService<Relationship> {
     super();
   }
 
-  getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
+  getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
     return this.halService.getEndpoint(linkPath);
   }
 
@@ -119,6 +93,14 @@ export class RelationshipService extends DataService<Relationship> {
     );
   }
 
+  /**
+   * Method to create a new relationship
+   * @param typeId The identifier of the relationship type
+   * @param item1 The first item of the relationship
+   * @param item2 The second item of the relationship
+   * @param leftwardValue The leftward value of the relationship
+   * @param rightwardValue The rightward value of the relationship
+   */
   addRelationship(typeId: string, item1: Item, item2: Item, leftwardValue?: string, rightwardValue?: string): Observable<RestResponse> {
     const options: HttpOptions = Object.create({});
     let headers = new HttpHeaders();
@@ -139,6 +121,10 @@ export class RelationshipService extends DataService<Relationship> {
     );
   }
 
+  /**
+   * Method to remove two items of a relationship from the cache using the identifier of the relationship
+   * @param relationshipId The identifier of the relationship
+   */
   private removeRelationshipItemsFromCacheByRelationship(relationshipId: string) {
     this.findById(relationshipId).pipe(
       getSucceededRemoteData(),
@@ -155,6 +141,10 @@ export class RelationshipService extends DataService<Relationship> {
     })
   }
 
+  /**
+   * Method to remove an item that's part of a relationship from the cache
+   * @param item The item to remove from the cache
+   */
   private removeRelationshipItemsFromCache(item) {
     this.objectCache.remove(item.self);
     this.requestService.removeByHrefSubstring(item.uuid);
@@ -229,7 +219,7 @@ export class RelationshipService extends DataService<Relationship> {
    * @param label
    * @param options
    */
-  getRelatedItemsByLabel(item: Item, label: string, options?: FindAllOptions): Observable<RemoteData<PaginatedList<Item>>> {
+  getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Item>>> {
     return this.getItemRelationshipsByLabel(item, label, options).pipe(paginatedRelationsToItems(item.uuid));
   }
 
@@ -240,18 +230,18 @@ export class RelationshipService extends DataService<Relationship> {
    * @param label
    * @param options
    */
-  getItemRelationshipsByLabel(item: Item, label: string, options?: FindAllOptions): Observable<RemoteData<PaginatedList<Relationship>>> {
-    let findAllOptions = new FindAllOptions();
+  getItemRelationshipsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Relationship>>> {
+    let findListOptions = new FindListOptions();
     if (options) {
-      findAllOptions = Object.assign(new FindAllOptions(), options);
+      findListOptions = Object.assign(new FindListOptions(), options);
     }
     const searchParams = [new SearchParam('label', label), new SearchParam('dso', item.id)];
-    if (findAllOptions.searchParams) {
-      findAllOptions.searchParams = [...findAllOptions.searchParams, ...searchParams];
+    if (findListOptions.searchParams) {
+      findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
     } else {
-      findAllOptions.searchParams = searchParams;
+      findListOptions.searchParams = searchParams;
     }
-    return this.searchBy('byLabel', findAllOptions);
+    return this.searchBy('byLabel', findListOptions);
   }
 
   /**
@@ -285,6 +275,12 @@ export class RelationshipService extends DataService<Relationship> {
     );
   }
 
+  /**
+   * Method to retrieve a relationship based on two items and a relationship type label
+   * @param item1 The first item in the relationship
+   * @param item2 The second item in the relationship
+   * @param label The rightward or leftward type of the relationship
+   */
   getRelationshipByItemsAndLabel(item1: Item, item2: Item, label: string): Observable<Relationship> {
     return this.getItemRelationshipsByLabel(item1, label)
       .pipe(
@@ -314,24 +310,51 @@ export class RelationshipService extends DataService<Relationship> {
     );
   }
 
+  /**
+   * Method to set the name variant for specific list and item
+   * @param listID The list for which to save the name variant
+   * @param itemID The item ID for which to save the name variant
+   * @param nameVariant The name variant to save
+   */
   public setNameVariant(listID: string, itemID: string, nameVariant: string) {
     this.appStore.dispatch(new SetNameVariantAction(listID, itemID, nameVariant));
   }
 
+  /**
+   * Method to retrieve the name variant for a specific list and item
+   * @param listID The list for which to retrieve the name variant
+   * @param itemID The item ID for which to retrieve the name variant
+   */
   public getNameVariant(listID: string, itemID: string): Observable<string> {
     return this.appStore.pipe(
       select(relationshipStateSelector(listID, itemID))
     );
   }
 
+  /**
+   * Method to remove the name variant for specific list and item
+   * @param listID The list for which to remove the name variant
+   * @param itemID The item ID for which to remove the name variant
+   */
   public removeNameVariant(listID: string, itemID: string) {
     this.appStore.dispatch(new RemoveNameVariantAction(listID, itemID));
   }
 
+  /**
+   * Method to retrieve all name variants for a single list
+   * @param listID The id of the list
+   */
   public getNameVariantsByListID(listID: string) {
     return this.appStore.pipe(select(relationshipListStateSelector(listID)));
   }
 
+  /**
+   * Method to update the name variant on the server
+   * @param item1 The first item of the relationship
+   * @param item2 The second item of the relationship
+   * @param relationshipLabel The leftward or rightward type of the relationship
+   * @param nameVariant The name variant to set for the matching relationship
+   */
   public updateNameVariant(item1: Item, item2: Item, relationshipLabel: string, nameVariant: string): Observable<RemoteData<Relationship>> {
     const update$ = this.getRelationshipByItemsAndLabel(item1, item2, relationshipLabel)
       .pipe(
diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts
index a7d11089df8082fb5b4c373e7c2531e64b1eab75..ca864f99dee71f9c33f1930dc02a616992c1783f 100644
--- a/src/app/core/data/request.models.ts
+++ b/src/app/core/data/request.models.ts
@@ -138,7 +138,7 @@ export class FindByIDRequest extends GetRequest {
   }
 }
 
-export class FindAllOptions {
+export class FindListOptions {
   scopeID?: string;
   elementsPerPage?: number;
   currentPage?: number;
@@ -147,11 +147,11 @@ export class FindAllOptions {
   startsWith?: string;
 }
 
-export class FindAllRequest extends GetRequest {
+export class FindListRequest extends GetRequest {
   constructor(
     uuid: string,
     href: string,
-    public body?: FindAllOptions,
+    public body?: FindListOptions,
   ) {
     super(uuid, href);
   }
diff --git a/src/app/core/data/resource-policy.service.ts b/src/app/core/data/resource-policy.service.ts
index 1574111232703f804a127d7fd0e86a5b7c3a9ecc..017e5cf5ee1b02652d99b9c998d26c194e34c2e2 100644
--- a/src/app/core/data/resource-policy.service.ts
+++ b/src/app/core/data/resource-policy.service.ts
@@ -6,7 +6,7 @@ import { Observable } from 'rxjs';
 
 import { DataService } from '../data/data.service';
 import { RequestService } from '../data/request.service';
-import { FindAllOptions } from '../data/request.models';
+import { FindListOptions } from '../data/request.models';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { ResourcePolicy } from '../shared/resource-policy.model';
 import { RemoteData } from '../data/remote-data';
@@ -36,7 +36,7 @@ class DataServiceImpl extends DataService<ResourcePolicy> {
     super();
   }
 
-  getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
+  getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
     return this.halService.getEndpoint(linkPath);
   }
 }
diff --git a/src/app/core/data/site-data.service.spec.ts b/src/app/core/data/site-data.service.spec.ts
index 3059ab9948a70966b4c2fb6ce9c193e3a2c2de5c..6148135f50ab269183f9bae94942c8840172a3ab 100644
--- a/src/app/core/data/site-data.service.spec.ts
+++ b/src/app/core/data/site-data.service.spec.ts
@@ -13,7 +13,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { of as observableOf } from 'rxjs';
 import { RestResponse } from '../cache/response.models';
 import { RequestEntry } from './request.reducer';
-import { FindAllOptions } from './request.models';
+import { FindListOptions } from './request.models';
 import { TestScheduler } from 'rxjs/testing';
 import { PaginatedList } from './paginated-list';
 import { RemoteData } from './remote-data';
@@ -31,7 +31,7 @@ describe('SiteDataService', () => {
   });
 
   const requestUUID = '34cfed7c-f597-49ef-9cbe-ea351f0023c2';
-  const options = Object.assign(new FindAllOptions(), {});
+  const options = Object.assign(new FindListOptions(), {});
 
   const getRequestEntry$ = (successful: boolean, statusCode: number, statusText: string) => {
     return observableOf({
diff --git a/src/app/core/data/site-data.service.ts b/src/app/core/data/site-data.service.ts
index ba395b40ed7860b4e2a334c53059ed9ebdef018c..c1a1b2069bdef7ffbc701f6dfaedb8526a578341 100644
--- a/src/app/core/data/site-data.service.ts
+++ b/src/app/core/data/site-data.service.ts
@@ -10,7 +10,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { NotificationsService } from '../../shared/notifications/notifications.service';
 import { HttpClient } from '@angular/common/http';
 import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
-import { FindAllOptions } from './request.models';
+import { FindListOptions } from './request.models';
 import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
 import { RemoteData } from './remote-data';
@@ -42,10 +42,10 @@ export class SiteDataService extends DataService<Site> {​
 
   /**
    * Get the endpoint for browsing the site object
-   * @param {FindAllOptions} options
+   * @param {FindListOptions} options
    * @param {Observable<string>} linkPath
    */
-  getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable<string> {
+  getBrowseEndpoint(options: FindListOptions, linkPath?: string): Observable<string> {
     return this.halService.getEndpoint(this.linkPath);
   }
 
diff --git a/src/app/core/eperson/eperson.service.ts b/src/app/core/eperson/eperson.service.ts
index 70ecf3f59e0d0a4a124383d8c33b0bdf810c3d39..81ae532e3b536bea8501805201036050963043fb 100644
--- a/src/app/core/eperson/eperson.service.ts
+++ b/src/app/core/eperson/eperson.service.ts
@@ -1,5 +1,5 @@
 import { Observable } from 'rxjs';
-import { FindAllOptions } from '../data/request.models';
+import { FindListOptions } from '../data/request.models';
 import { DataService } from '../data/data.service';
 import { CacheableObject } from '../cache/object-cache.reducer';
 
@@ -8,7 +8,7 @@ import { CacheableObject } from '../cache/object-cache.reducer';
  */
 export abstract class EpersonService<TDomain extends CacheableObject> extends DataService<TDomain> {
 
-  public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
+  public getBrowseEndpoint(options: FindListOptions): Observable<string> {
     return this.halService.getEndpoint(this.linkPath);
   }
 }
diff --git a/src/app/core/eperson/group-eperson.service.ts b/src/app/core/eperson/group-eperson.service.ts
index 2014e6120a20a3811f7ca495aac886bc6a9658bf..c8a2a78917198ab325ce699746551d21328085a9 100644
--- a/src/app/core/eperson/group-eperson.service.ts
+++ b/src/app/core/eperson/group-eperson.service.ts
@@ -7,7 +7,7 @@ import { filter, map, take } from 'rxjs/operators';
 
 import { EpersonService } from './eperson.service';
 import { RequestService } from '../data/request.service';
-import { FindAllOptions } from '../data/request.models';
+import { FindListOptions } from '../data/request.models';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
 import { Group } from './models/group.model';
 import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -52,7 +52,7 @@ export class GroupEpersonService extends EpersonService<Group> {
    */
   isMemberOf(groupName: string): Observable<boolean> {
     const searchHref = 'isMemberOf';
-    const options = new FindAllOptions();
+    const options = new FindListOptions();
     options.searchParams = [new SearchParam('groupName', groupName)];
 
     return this.searchBy(searchHref, options).pipe(
diff --git a/src/app/core/services/route.service.ts b/src/app/core/services/route.service.ts
index 8bca76f7d29798e9f771f333eeccf298e76ab002..b29c491cb09ab486e664ae196e1b33707837f939 100644
--- a/src/app/core/services/route.service.ts
+++ b/src/app/core/services/route.service.ts
@@ -195,6 +195,9 @@ export class RouteService {
     this.store.dispatch(new SetParameterAction(key, value));
   }
 
+  /**
+   * Sets the current route parameters and query parameters in the store
+   */
   public setCurrentRouteInfo() {
     combineLatest(this.getRouteParams(), this.route.queryParams)
       .pipe(take(1))
diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts
index a93d54db64974b1e86d95c54e8ce81aa6d1d4f69..117cc074ca995f44be17c5048ce1bb720bc2cf2c 100644
--- a/src/app/core/shared/hal-endpoint.service.ts
+++ b/src/app/core/shared/hal-endpoint.service.ts
@@ -43,8 +43,8 @@ export class HALEndpointService {
       );
   }
 
-  public getEndpoint(linkPath: string): Observable<string> {
-    return this.getEndpointAt(this.getRootHref(), ...linkPath.split('/'));
+  public getEndpoint(linkPath: string, startHref?: string): Observable<string> {
+    return this.getEndpointAt(startHref || this.getRootHref(), ...linkPath.split('/'));
   }
 
   /**
diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts
index 43c4aecafefa108f08b6e7d241a759ef35f9384c..47195ed0a11ef682f057930ca77ba739fb3caff7 100644
--- a/src/app/core/submission/workflowitem-data.service.ts
+++ b/src/app/core/submission/workflowitem-data.service.ts
@@ -8,7 +8,7 @@ import { DataService } from '../data/data.service';
 import { RequestService } from '../data/request.service';
 import { WorkflowItem } from './models/workflowitem.model';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
-import { FindAllOptions } from '../data/request.models';
+import { FindListOptions } from '../data/request.models';
 import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
 import { NotificationsService } from '../../shared/notifications/notifications.service';
 import { ObjectCacheService } from '../cache/object-cache.service';
@@ -35,7 +35,7 @@ export class WorkflowItemDataService extends DataService<WorkflowItem> {
     super();
   }
 
-  public getBrowseEndpoint(options: FindAllOptions) {
+  public getBrowseEndpoint(options: FindListOptions) {
     return this.halService.getEndpoint(this.linkPath);
   }
 
diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts
index 4d388ec5138cc2fdf7e0d5f5d0ddcccd2afb6552..3f782b74a2fd3813c5b7ab117e12a50ea5e993b6 100644
--- a/src/app/core/submission/workspaceitem-data.service.ts
+++ b/src/app/core/submission/workspaceitem-data.service.ts
@@ -7,7 +7,7 @@ import { CoreState } from '../core.reducers';
 import { DataService } from '../data/data.service';
 import { RequestService } from '../data/request.service';
 import { HALEndpointService } from '../shared/hal-endpoint.service';
-import { FindAllOptions } from '../data/request.models';
+import { FindListOptions } from '../data/request.models';
 import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
 import { NotificationsService } from '../../shared/notifications/notifications.service';
 import { ObjectCacheService } from '../cache/object-cache.service';
@@ -35,7 +35,7 @@ export class WorkspaceitemDataService extends DataService<WorkspaceItem> {
     super();
   }
 
-  public getBrowseEndpoint(options: FindAllOptions) {
+  public getBrowseEndpoint(options: FindListOptions) {
     return this.halService.getEndpoint(this.linkPath);
   }
 
diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts
index f39b144c6aab45a9c550d2c30a4d72a661021989..cf23bfd74b88c32ff4515e063e1cb9fc5185a3ce 100644
--- a/src/app/core/tasks/tasks.service.ts
+++ b/src/app/core/tasks/tasks.service.ts
@@ -4,7 +4,7 @@ import { merge as observableMerge, Observable, of as observableOf } from 'rxjs';
 import { distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/operators';
 
 import { DataService } from '../data/data.service';
-import { DeleteRequest, FindAllOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models';
+import { DeleteRequest, FindListOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models';
 import { isNotEmpty } from '../../shared/empty.util';
 import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
 import { ProcessTaskResponse } from './models/process-task-response';
@@ -18,7 +18,7 @@ import { CacheableObject } from '../cache/object-cache.reducer';
  */
 export abstract class TasksService<T extends CacheableObject> extends DataService<T> {
 
-  public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
+  public getBrowseEndpoint(options: FindListOptions): Observable<string> {
     return this.halService.getEndpoint(this.linkPath);
   }
 
diff --git a/src/app/core/utilities/equals.decorators.ts b/src/app/core/utilities/equals.decorators.ts
index 6dde05922e48f08f494cfea164165deb946e3c10..6fdbd40c0fce36625b8ad9eb03a6995e89f22841 100644
--- a/src/app/core/utilities/equals.decorators.ts
+++ b/src/app/core/utilities/equals.decorators.ts
@@ -5,6 +5,10 @@ import { EquatableObject } from './equatable';
 const excludedFromEquals = new Map();
 const fieldsForEqualsMap = new Map();
 
+/**
+ * Decorator function that adds the equatable settings from the given (parent) object
+ * @param parentCo The constructor of the parent object
+ */
 export function inheritEquatable(parentCo: GenericConstructor<EquatableObject<any>>) {
   return function decorator(childCo: GenericConstructor<EquatableObject<any>>) {
     const parentExcludedFields = getExcludedFromEqualsFor(parentCo) || [];
@@ -21,6 +25,11 @@ export function inheritEquatable(parentCo: GenericConstructor<EquatableObject<an
   }
 }
 
+/**
+ * Function to mark properties as excluded from the equals method
+ * @param object The object to exclude the property for
+ * @param propertyName The name of the property to exclude
+ */
 export function excludeFromEquals(object: any, propertyName: string): any {
   if (!object) {
     return;
@@ -37,6 +46,10 @@ export function getExcludedFromEqualsFor(constructor: Function): string[] {
   return excludedFromEquals.get(constructor) || [];
 }
 
+/**
+ * Function to save the fields that are to be used for a certain property in the equals method for the given object
+ * @param fields The fields to use to equate the property of the object
+ */
 export function fieldsForEquals(...fields: string[]): any {
   return function i(object: any, propertyName: string): any {
     if (!object) {
diff --git a/src/app/core/utilities/equatable.ts b/src/app/core/utilities/equatable.ts
index 1029a295ba2f4979a5b67c4da944fb5338be8aab..e022297229b33d3a40e7d44753bd0485b911c2c8 100644
--- a/src/app/core/utilities/equatable.ts
+++ b/src/app/core/utilities/equatable.ts
@@ -1,6 +1,12 @@
 import { getExcludedFromEqualsFor, getFieldsForEquals } from './equals.decorators';
 import { hasNoValue, hasValue } from '../../shared/empty.util';
 
+/**
+ * Method to compare fields of two objects against each other
+ * @param object1 The first object for the comparison
+ * @param object2 The second object for the comparison
+ * @param fieldList The list of property/field names to compare
+ */
 function equalsByFields(object1, object2, fieldList): boolean {
   const unequalProperty = fieldList.find((key) => {
     if (object1[key] === object2[key]) {
@@ -27,6 +33,10 @@ function equalsByFields(object1, object2, fieldList): boolean {
   return hasNoValue(unequalProperty);
 }
 
+/**
+ * Abstract class to represent objects that can be compared to each other
+ * It provides a default way of comparing
+ */
 export abstract class EquatableObject<T> {
   equals(other: T): boolean {
     if (hasNoValue(other)) {
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
index ae6c3a8914785ee8edbd16892bf4da56ceb50be9..e86ab35e0ee62235de5199e3790518da3b936119 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
+++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
@@ -36,8 +36,11 @@
     </div>
   </div>
   <div class="mt-5 w-100">
-    <ds-related-entities-search [item]="object"
-      [relationType]="'isJournalOfPublication'">
-    </ds-related-entities-search>
+    <ds-tabbed-related-entities-search  [item]="object"
+                                        [relationTypes]="[{
+                                          label: 'isJournalOfPublication',
+                                          filter: 'isJournalOfPublication'
+                                        }]">
+    </ds-tabbed-related-entities-search>
   </div>
 </div>
diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
index 4d97868b585787d772addbd07a226dc2c11c3da1..1b23d567f529ba3c5bbe756edc6d6acfd5bcb26d 100644
--- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
+++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
@@ -24,16 +24,6 @@
     </ds-generic-item-page-field>
   </div>
   <div class="col-xs-12 col-md-6">
-    <ds-related-items
-      [parentItem]="object"
-      [relationType]="'isPersonOfOrgUnit'"
-      [label]="'relationships.isPersonOf' | translate">
-    </ds-related-items>
-    <ds-related-items
-      [parentItem]="object"
-      [relationType]="'isProjectOfOrgUnit'"
-      [label]="'relationships.isProjectOf' | translate">
-    </ds-related-items>
     <ds-related-items
       [parentItem]="object"
       [relationType]="'isPublicationOfOrgUnit'"
@@ -49,4 +39,18 @@
       </a>
     </div>
   </div>
+  <div class="mt-5 w-100">
+    <ds-tabbed-related-entities-search  [item]="object"
+                                        [relationTypes]="[{
+                                          label: 'isOrgUnitOfPerson',
+                                          filter: 'isOrgUnitOfPerson',
+                                          configuration: 'person'
+                                        },
+                                        {
+                                          label: 'isOrgUnitOfProject',
+                                          filter: 'isOrgUnitOfProject',
+                                          configuration: 'project'
+                                        }]">
+    </ds-tabbed-related-entities-search>
+  </div>
 </div>
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html
index ff675ab0575efecdb3043233b37363479c279b04..97a3cf416ef252ac26d86c5492b4adc9ef93afad 100644
--- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html
+++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html
@@ -53,8 +53,11 @@
     </div>
   </div>
   <div class="mt-5 w-100">
-    <ds-related-entities-search [item]="object"
-      [relationType]="'isAuthorOfPublication'">
-    </ds-related-entities-search>
+    <ds-tabbed-related-entities-search  [item]="object"
+                                        [relationTypes]="[{
+                                          label: 'isAuthorOfPublication',
+                                          filter: 'isAuthorOfPublication'
+                                        }]">
+    </ds-tabbed-related-entities-search>
   </div>
 </div>
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts
index 4a2a41aae5157a64fc527fecd993a6ef1a17ba8c..cbddb8d6f9c535f37c5178881ef0e5c49d72333c 100644
--- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts
@@ -24,7 +24,7 @@ import { NameVariantModalComponent } from '../../name-variant-modal/name-variant
 })
 
 /**
- * The component for displaying a list element for an item search result of the type Person
+ * The component for displaying a list element for an item search result of the type OrgUnit
  */
 export class OrgUnitSearchResultListSubmissionElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> implements OnInit {
   allSuggestions: string[];
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.spec.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..34b89cc8aacc0981c2df345ffac137382ab7c87d
--- /dev/null
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.spec.ts
@@ -0,0 +1,64 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { OrgUnitInputSuggestionsComponent } from './org-unit-input-suggestions.component';
+import { FormsModule } from '@angular/forms';
+
+let component: OrgUnitInputSuggestionsComponent;
+let fixture: ComponentFixture<OrgUnitInputSuggestionsComponent>;
+
+let suggestions: string[];
+let testValue;
+
+function init() {
+  suggestions = ['test', 'suggestion', 'example']
+  testValue = 'bla';
+}
+
+describe('OrgUnitInputSuggestionsComponent', () => {
+  beforeEach(async(() => {
+    init();
+    TestBed.configureTestingModule({
+      declarations: [OrgUnitInputSuggestionsComponent],
+      imports: [
+        FormsModule,
+      ],
+      providers: [
+      ],
+      schemas: [NO_ERRORS_SCHEMA]
+    }).overrideComponent(OrgUnitInputSuggestionsComponent, {
+      set: { changeDetection: ChangeDetectionStrategy.Default }
+    }).compileComponents();
+  }));
+
+  beforeEach(async(() => {
+    fixture = TestBed.createComponent(OrgUnitInputSuggestionsComponent);
+    component = fixture.componentInstance;
+    component.suggestions = suggestions;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('When the component is initialized', () => {
+    it('should set the value to the first value of the suggestions', () => {
+      expect(component.value).toEqual('test');
+    });
+  });
+
+  describe('When onSubmit is called', () => {
+    it('should set the value to parameter of the method', () => {
+      component.onSubmit(testValue);
+      expect(component.value).toEqual(testValue);
+    });
+  });
+
+  describe('When onClickSuggestion is called', () => {
+    it('should set the value to parameter of the method', () => {
+      component.onClickSuggestion(testValue);
+      expect(component.value).toEqual(testValue);
+    });
+  });
+
+});
diff --git a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.html b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.html
index 9d2139621c27c2127719610f8ae1b1afcab5f154..13ae884ccb4d8b7bade6f8e693c84e4ede9684a7 100644
--- a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.html
+++ b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.html
@@ -8,6 +8,6 @@
     {{'submission.sections.describe.relationship-lookup.name-variant.notification.content' | translate: { value: value } }}
 </div>
 <div class="modal-footer justify-content-between">
-    <button type="button" class="btn btn-light" (click)="modal.close()">{{'submission.sections.describe.relationship-lookup.name-variant.notification.confirm' | translate }}</button>
-    <button type="button" class="btn btn-light" (click)="modal.dismiss()">{{'submission.sections.describe.relationship-lookup.name-variant.notification.decline' | translate }}</button>
+    <button type="button" class="btn btn-light confirm-button" (click)="modal.close()">{{'submission.sections.describe.relationship-lookup.name-variant.notification.confirm' | translate }}</button>
+    <button type="button" class="btn btn-light decline-button" (click)="modal.dismiss()">{{'submission.sections.describe.relationship-lookup.name-variant.notification.decline' | translate }}</button>
 </div>
diff --git a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.spec.ts b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.spec.ts
index 4af7b8161a8c6ba6916fc44acfd509fa997d7bab..b5043ea2d67e2b37f18a06f9cc46c4a15417bbc5 100644
--- a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.spec.ts
+++ b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.spec.ts
@@ -3,16 +3,23 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { NameVariantModalComponent } from './name-variant-modal.component';
 import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap';
 import { TranslateModule } from '@ngx-translate/core';
+import { By } from '@angular/platform-browser';
 
 describe('NameVariantModalComponent', () => {
   let component: NameVariantModalComponent;
   let fixture: ComponentFixture<NameVariantModalComponent>;
+  let debugElement;
+  let modal;
 
+  function init() {
+    modal = jasmine.createSpyObj('modal', ['close', 'dismiss']);
+  }
   beforeEach(async(() => {
+    init();
     TestBed.configureTestingModule({
       declarations: [NameVariantModalComponent],
       imports: [NgbModule.forRoot(), TranslateModule.forRoot()],
-      providers: [NgbActiveModal]
+      providers: [{ provide: NgbActiveModal, useValue: modal }]
     })
       .compileComponents();
   }));
@@ -20,10 +27,27 @@ describe('NameVariantModalComponent', () => {
   beforeEach(() => {
     fixture = TestBed.createComponent(NameVariantModalComponent);
     component = fixture.componentInstance;
+    debugElement = fixture.debugElement;
     fixture.detectChanges();
+
   });
 
   it('should create', () => {
     expect(component).toBeTruthy();
   });
+
+  it('when close button is clicked, dismiss should be called on the modal', () => {
+    debugElement.query(By.css('button.close')).triggerEventHandler('click', {});
+    expect(modal.dismiss).toHaveBeenCalled();
+  });
+
+  it('when confirm button is clicked, close should be called on the modal', () => {
+    debugElement.query(By.css('button.confirm-button')).triggerEventHandler('click', {});
+    expect(modal.close).toHaveBeenCalled();
+  });
+
+  it('when decline button is clicked, dismiss should be called on the modal', () => {
+    debugElement.query(By.css('button.decline-button')).triggerEventHandler('click', {});
+    expect(modal.dismiss).toHaveBeenCalled();
+  });
 });
diff --git a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.ts b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.ts
index 34eab47b4700c9872059dbf133d8819a414ef9bd..75817d786a3bfb1495b3c1c3b9ce6713dce16526 100644
--- a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.ts
+++ b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.ts
@@ -1,6 +1,10 @@
 import { Component, Input } from '@angular/core';
 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 
+/**
+ * This component a pop up for when the user selects a custom name variant during submission for a relationship$
+ * The user can either choose to decline or accept to save the name variant as a metadata in the entity
+ */
 @Component({
   selector: 'ds-name-variant-modal',
   templateUrl: './name-variant-modal.component.html',
diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts
index 4c7c3cd0302b89ee4661399498282ba6e9ac7a7a..b2ba10fb989c4f7c2d503bda42eb751cb212b5d2 100644
--- a/src/app/navbar/navbar.component.ts
+++ b/src/app/navbar/navbar.component.ts
@@ -53,17 +53,18 @@ export class NavbarComponent extends MenuComponent implements OnInit {
         } as TextMenuItemModel,
         index: 0
       },
-      // {
-      //   id: 'browse_global_communities_and_collections',
-      //   parentID: 'browse_global',
-      //   active: false,
-      //   visible: true,
-      //   model: {
-      //     type: MenuItemType.LINK,
-      //     text: 'menu.section.browse_global_communities_and_collections',
-      //     link: '#'
-      //   } as LinkMenuItemModel,
-      // },
+      /* Communities & Collections tree */
+      {
+        id: `browse_global_communities_and_collections`,
+        parentID: 'browse_global',
+        active: false,
+        visible: true,
+        model: {
+          type: MenuItemType.LINK,
+          text: `menu.section.browse_global_communities_and_collections`,
+          link: `/community-list`
+        } as LinkMenuItemModel
+      },
 
       /* Statistics */
       {
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
index f1b8bd40b58549bd5e38444649da1356531eb982..c8903b19eea4608d472ba1e9a68cf648aeeede11 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
@@ -225,6 +225,9 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
     super(componentFactoryResolver, layoutService, validationService);
   }
 
+  /**
+   * Sets up the necessary variables for when this control can be used to add relationships to the submitted item
+   */
   ngOnInit(): void {
     this.hasRelationLookup = hasValue(this.model.relationship);
     this.reorderables = [];
@@ -324,6 +327,9 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
     return this.model.value.pipe(map((list: Array<SearchResult<DSpaceObject>>) => isNotEmpty(list)));
   }
 
+  /**
+   * Open a modal where the user can select relationships to be added to item being submitted
+   */
   openLookup() {
     this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, {
       size: 'lg'
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8e0c6fc20eceb8ac9823ec134fa0e363a7217dd6
--- /dev/null
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts
@@ -0,0 +1,58 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
+import { DsDynamicDisabledComponent } from './dynamic-disabled.component';
+import { FormsModule } from '@angular/forms';
+import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
+import { DynamicDisabledModel } from './dynamic-disabled.model';
+import { By } from '@angular/platform-browser';
+import { TranslateModule } from '@ngx-translate/core';
+
+describe('DsDynamicDisabledComponent', () => {
+  let comp: DsDynamicDisabledComponent;
+  let fixture: ComponentFixture<DsDynamicDisabledComponent>;
+  let de: DebugElement;
+  let el: HTMLElement;
+  let model;
+
+  function init() {
+    model = new DynamicDisabledModel({ value: 'test', repeatable: false, metadataFields: [], submissionId: '1234', id: '1' });
+  }
+
+  beforeEach(async(() => {
+    init();
+    TestBed.configureTestingModule({
+      declarations: [DsDynamicDisabledComponent],
+      imports: [FormsModule, TranslateModule.forRoot()],
+      providers: [
+        {
+          provide: DynamicFormLayoutService,
+          useValue: {}
+        },
+        {
+          provide: DynamicFormValidationService,
+          useValue: {}
+        }
+      ],
+      schemas: [NO_ERRORS_SCHEMA]
+    }).compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DsDynamicDisabledComponent);
+    comp = fixture.componentInstance; // DsDynamicDisabledComponent test instance
+    de = fixture.debugElement;
+    el = de.nativeElement;
+    comp.model = model;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(comp).toBeTruthy();
+  });
+
+  it('should have a disabled input', () => {
+    const input = de.query(By.css('input'));
+    console.log(input.nativeElement.getAttribute('disabled'));
+    expect(input.nativeElement.getAttribute('disabled')).toEqual('');
+  });
+});
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts
index 173509acf9791c47c35bcfc54e973a7808a71a4d..490be050efa20c9dd046650b3fbcf9d63d642725 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts
@@ -3,8 +3,10 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
 import { DynamicFormControlComponent, DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
 import { FormGroup } from '@angular/forms';
 import { DynamicDisabledModel } from './dynamic-disabled.model';
-import { RelationshipTypeService } from '../../../../../../core/data/relationship-type.service';
 
+/**
+ * Component representing a simple disabled input field
+ */
 @Component({
   selector: 'ds-dynamic-disabled',
   templateUrl: './dynamic-disabled.component.html'
@@ -21,8 +23,7 @@ export class DsDynamicDisabledComponent extends DynamicFormControlComponent {
   @Output() focus: EventEmitter<any> = new EventEmitter<any>();
 
   constructor(protected layoutService: DynamicFormLayoutService,
-              protected validationService: DynamicFormValidationService,
-              protected relationshipTypeService: RelationshipTypeService
+              protected validationService: DynamicFormValidationService
   ) {
     super(layoutService, validationService);
   }
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts
index eb1f3660e62fa68849955d2310682684b84464b8..0fa2b3e5ed27f07887b376ca8fe33fb2f64e18c6 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts
@@ -7,6 +7,9 @@ export interface DsDynamicDisabledModelConfig extends DsDynamicInputModelConfig
   value?: any;
 }
 
+/**
+ * This model represents the data for a disabled input field
+ */
 export class DynamicDisabledModel extends DsDynamicInputModel {
 
   @serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_DISABLED;
@@ -14,7 +17,6 @@ export class DynamicDisabledModel extends DsDynamicInputModel {
 
   constructor(config: DsDynamicDisabledModelConfig, layout?: DynamicFormControlLayout) {
     super(config, layout);
-
     this.readOnly = true;
     this.disabled = true;
     this.valueUpdates.next(config.value);
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts
index 9937fb6010954004ddcc2b6765ca22cc757f602b..4aab8ff32528b8d3d0077a28e33b74c45b7d7d06 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts
@@ -30,6 +30,9 @@ import { Context } from '../../../../../core/shared/context.model';
   ]
 })
 
+/**
+ * Represents a modal where the submitter can select items to be added as a certain relationship type to the object being submitted
+ */
 export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy {
   label: string;
   relationshipOptions: RelationshipOptions;
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions.ts
index dbd0938945dbcfa1aa6b77685dac8ea4e5bdfccc..f32836eef16169790168c58bee9da998a9721376 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions.ts
@@ -11,7 +11,9 @@ export const NameVariantActionTypes = {
 };
 
 /* tslint:disable:max-classes-per-file */
-
+/**
+ * Abstract class for actions that happen to name variants
+ */
 export abstract class NameVariantListAction implements Action {
   type;
   payload: {
@@ -24,6 +26,9 @@ export abstract class NameVariantListAction implements Action {
   }
 }
 
+/**
+ * Action for setting a new name on an item in a certain list
+ */
 export class SetNameVariantAction extends NameVariantListAction {
   type = NameVariantActionTypes.SET_NAME_VARIANT;
   payload: {
@@ -38,6 +43,9 @@ export class SetNameVariantAction extends NameVariantListAction {
   }
 }
 
+/**
+ * Action for removing a name on an item in a certain list
+ */
 export class RemoveNameVariantAction extends NameVariantListAction {
   type = NameVariantActionTypes.REMOVE_NAME_VARIANT;
   constructor(listID: string, itemID: string) {
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts
index a17e042c2b5f38d86d1a265d6558f5ac0a38716e..e26abf94c1de7dc57a3f2a2e8384e7985ff44f72 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts
@@ -73,6 +73,11 @@ export class RelationshipEffects {
       )
     );
 
+  /**
+   * Updates the namevariant in a relationship
+   * If the relationship is currently being added or removed, it will add the name variant to an update map so it will be sent with the next add request instead
+   * Otherwise the update is done immediately
+   */
   @Effect({ dispatch: false }) updateNameVariantsActions$ = this.actions$
     .pipe(
       ofType(RelationshipActionTypes.UPDATE_RELATIONSHIP),
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts
index c7bb7104b5b0622485f332885d3259261b593459..9c00d64953da5b29ea5c12f997f9e80a1e2d07b9 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts
@@ -33,6 +33,9 @@ import { Context } from '../../../../../../core/shared/context.model';
   ]
 })
 
+/**
+ * Tab for inside the lookup model that represents the items that can be used as a relationship in this submission
+ */
 export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDestroy {
   @Input() relationship: RelationshipOptions;
   @Input() listId: string;
@@ -63,6 +66,9 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
   ) {
   }
 
+  /**
+   * Sets up the pagination and fixed query parameters
+   */
   ngOnInit(): void {
     this.resetRoute();
     this.routeService.setParameter('fixedFilterQuery', this.relationship.filter);
@@ -90,12 +96,19 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
     );
   }
 
+  /**
+   * Method to reset the route when the window is opened to make sure no strange pagination issues appears
+   */
   resetRoute() {
     this.router.navigate([], {
       queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }),
     });
   }
 
+  /**
+   * Selects a page in the store
+   * @param page The page to select
+   */
   selectPage(page: Array<SearchResult<Item>>) {
     this.selection$
       .pipe(take(1))
@@ -106,6 +119,10 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
     this.selectableListService.select(this.listId, page);
   }
 
+  /**
+   * Deselects a page in the store
+   * @param page the page to deselect
+   */
   deselectPage(page: Array<SearchResult<Item>>) {
     this.allSelected = false;
     this.selection$
@@ -117,6 +134,9 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
     this.selectableListService.deselect(this.listId, page);
   }
 
+  /**
+   * Select all items that were found using the current search query
+   */
   selectAll() {
     this.allSelected = true;
     this.selectAllLoading = true;
@@ -142,6 +162,9 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
     );
   }
 
+  /**
+   * Deselect all items
+   */
   deselectAll() {
     this.allSelected = false;
     this.selection$
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts
index b47207a957c68fca4db5952cdbaa07cdb56ddde9..8aa3dc3828743342acbd86964e433aa90e09ec45 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts
@@ -25,6 +25,9 @@ import { Context } from '../../../../../../core/shared/context.model';
   ]
 })
 
+/**
+ * Tab for inside the lookup model that represents the currently selected relationships
+ */
 export class DsDynamicLookupRelationSelectionTabComponent {
   @Input() label: string;
   @Input() listId: string;
@@ -44,6 +47,9 @@ export class DsDynamicLookupRelationSelectionTabComponent {
               private searchConfigService: SearchConfigurationService) {
   }
 
+  /**
+   * Set up the selection and pagination on load
+   */
   ngOnInit() {
     this.resetRoute();
     this.selectionRD$ = this.searchConfigService.paginatedSearchOptions
@@ -70,6 +76,9 @@ export class DsDynamicLookupRelationSelectionTabComponent {
       )
   }
 
+  /**
+   * Method to reset the route when the window is opened to make sure no strange pagination issues appears
+   */
   resetRoute() {
     this.router.navigate([], {
       queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }),
diff --git a/src/app/shared/form/builder/models/relationship-options.model.ts b/src/app/shared/form/builder/models/relationship-options.model.ts
index 7d9542794b1bd8a875537b7aea0e2971ac624b57..f1d3d0ae7ad8879b24bb891b8598347a7bc2b4f7 100644
--- a/src/app/shared/form/builder/models/relationship-options.model.ts
+++ b/src/app/shared/form/builder/models/relationship-options.model.ts
@@ -1,5 +1,8 @@
 const RELATION_METADATA_PREFIX = 'relation.'
 
+/**
+ * The submission options for fields that can represent relationships
+ */
 export class RelationshipOptions {
   relationshipType: string;
   filter: string;
diff --git a/src/app/shared/form/builder/parsers/disabled-field-parser.spec.ts b/src/app/shared/form/builder/parsers/disabled-field-parser.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7dce05f18d8fce6eb72b5813d0fdadd896b2d8ef
--- /dev/null
+++ b/src/app/shared/form/builder/parsers/disabled-field-parser.spec.ts
@@ -0,0 +1,66 @@
+import { FormFieldModel } from '../models/form-field.model';
+import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
+import { ParserOptions } from './parser-options';
+import { DisabledFieldParser } from './disabled-field-parser';
+import { DynamicDisabledModel } from '../ds-dynamic-form-ui/models/disabled/dynamic-disabled.model';
+
+describe('DisabledFieldParser test suite', () => {
+  let field: FormFieldModel;
+  let initFormValues: any = {};
+
+  const submissionId = '1234';
+  const parserOptions: ParserOptions = {
+    readOnly: false,
+    submissionScope: null,
+    authorityUuid: null
+  };
+
+  beforeEach(() => {
+    field = {
+      input: {
+        type: ''
+      },
+      label: 'Description',
+      mandatory: 'false',
+      repeatable: false,
+      hints: 'Enter a description.',
+      selectableMetadata: [
+        {
+          metadata: 'description'
+        }
+      ],
+      languageCodes: []
+    } as FormFieldModel;
+
+  });
+
+  it('should init parser properly', () => {
+    const parser = new DisabledFieldParser(submissionId, field, initFormValues, parserOptions);
+
+    expect(parser instanceof DisabledFieldParser).toBe(true);
+  });
+
+  it('should return a DynamicDisabledModel object when repeatable option is false', () => {
+    const parser = new DisabledFieldParser(submissionId, field, initFormValues, parserOptions);
+
+    const fieldModel = parser.parse();
+
+    expect(fieldModel instanceof DynamicDisabledModel).toBe(true);
+  });
+
+  it('should set init value properly', () => {
+    initFormValues = {
+      description: [
+        new FormFieldMetadataValueObject('test description'),
+      ],
+    };
+    const expectedValue ='test description';
+
+    const parser = new DisabledFieldParser(submissionId, field, initFormValues, parserOptions);
+
+    const fieldModel = parser.parse();
+    console.log(fieldModel);
+    expect(fieldModel.value).toEqual(expectedValue);
+  });
+
+});
diff --git a/src/app/shared/form/builder/parsers/disabled-field-parser.ts b/src/app/shared/form/builder/parsers/disabled-field-parser.ts
index 5cccff45912a49926267f87425917d3af86ce283..db3e4ac8b979df915a897785462197cf2fe087da 100644
--- a/src/app/shared/form/builder/parsers/disabled-field-parser.ts
+++ b/src/app/shared/form/builder/parsers/disabled-field-parser.ts
@@ -2,10 +2,15 @@ import { FieldParser } from './field-parser';
 import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
 import { DsDynamicDisabledModelConfig, DynamicDisabledModel } from '../ds-dynamic-form-ui/models/disabled/dynamic-disabled.model';
 
+/**
+ * Field parser for disabled fields
+ */
 export class DisabledFieldParser extends FieldParser {
 
   public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any {
+    console.log(fieldValue);
     const emptyModelConfig: DsDynamicDisabledModelConfig = this.initModel(null, label);
+    this.setValues(emptyModelConfig, fieldValue);
     return new DynamicDisabledModel(emptyModelConfig)
   }
 }
diff --git a/src/app/shared/form/builder/parsers/parser-factory.ts b/src/app/shared/form/builder/parsers/parser-factory.ts
index 1d3ace320ff663034833f38a539896256d8c55de..d674007da45c190274a9728aa18b5bc6432b95e7 100644
--- a/src/app/shared/form/builder/parsers/parser-factory.ts
+++ b/src/app/shared/form/builder/parsers/parser-factory.ts
@@ -27,6 +27,9 @@ const fieldParserDeps = [
   PARSER_OPTIONS,
 ];
 
+/**
+ * Method to retrieve a field parder with its providers based on the input type
+ */
 export class ParserFactory {
   public static getProvider(type: ParserType): StaticProvider {
     switch (type) {
diff --git a/src/app/shared/form/builder/parsers/row-parser.ts b/src/app/shared/form/builder/parsers/row-parser.ts
index 72737cfaa93051beb0dcd8e2f5a8e10c4908a659..4938b9859ec0f8aca4ae61cde3d5c1cd1d4f4bbd 100644
--- a/src/app/shared/form/builder/parsers/row-parser.ts
+++ b/src/app/shared/form/builder/parsers/row-parser.ts
@@ -27,6 +27,10 @@ export const ROW_ID_PREFIX = 'df-row-group-config-';
 @Injectable({
   providedIn: 'root'
 })
+
+/**
+ * Parser the submission data for a single row
+ */
 export class RowParser {
   constructor(private parentInjector: Injector) {
   }
diff --git a/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts b/src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts
similarity index 93%
rename from src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts
rename to src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts
index a53762e8ce04be3dbe42265f40a36d3aace4e609..e0dae08470177aeff57004d66db3a92609d465c4 100644
--- a/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts
+++ b/src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts
@@ -1,7 +1,7 @@
 import { HttpHeaders, HttpResponse } from '@angular/common/http';
 import { of as observableOf } from 'rxjs';
-import { GlobalConfig } from '../../../config/global-config.interface';
-import { RestRequestMethod } from '../data/rest-request-method';
+import { GlobalConfig } from '../../../../config/global-config.interface';
+import { RestRequestMethod } from '../../../core/data/rest-request-method';
 import { EndpointMockingRestService } from './endpoint-mocking-rest.service';
 import { MockResponseMap } from './mocks/mock-response-map';
 
diff --git a/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.ts b/src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.ts
similarity index 85%
rename from src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.ts
rename to src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.ts
index 86ec5986c65da4fbd419f6580dd67b8d53157e1d..b0e89b80b5dcf177af0c1670a7b034c78d2a1b1c 100644
--- a/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.ts
+++ b/src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.ts
@@ -1,12 +1,12 @@
 import { HttpClient, HttpHeaders } from '@angular/common/http'
 import { Inject, Injectable } from '@angular/core';
 import { Observable, of as observableOf } from 'rxjs';
-import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
-import { isEmpty } from '../../shared/empty.util';
-import { RestRequestMethod } from '../data/rest-request-method';
+import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
+import { isEmpty } from '../../empty.util';
+import { RestRequestMethod } from '../../../core/data/rest-request-method';
 
-import { DSpaceRESTV2Response } from './dspace-rest-v2-response.model';
-import { DSpaceRESTv2Service, HttpOptions } from './dspace-rest-v2.service';
+import { DSpaceRESTV2Response } from '../../../core/dspace-rest-v2/dspace-rest-v2-response.model';
+import { DSpaceRESTv2Service, HttpOptions } from '../../../core/dspace-rest-v2/dspace-rest-v2.service';
 import { MOCK_RESPONSE_MAP, MockResponseMap } from './mocks/mock-response-map';
 import * as URL from 'url-parse';
 
@@ -14,6 +14,8 @@ import * as URL from 'url-parse';
  * Service to access DSpace's REST API.
  *
  * If a URL is found in this.mockResponseMap, it returns the mock response instead
+ * This service can be used for mocking REST responses when developing new features
+ * This is especially useful, when a REST endpoint is broken or does not exist yet
  */
 @Injectable()
 export class EndpointMockingRestService extends DSpaceRESTv2Service {
diff --git a/src/app/core/dspace-rest-v2/mocks/mock-response-map.ts b/src/app/shared/mocks/dspace-rest-v2/mocks/mock-response-map.ts
similarity index 82%
rename from src/app/core/dspace-rest-v2/mocks/mock-response-map.ts
rename to src/app/shared/mocks/dspace-rest-v2/mocks/mock-response-map.ts
index cea526b078f313c2248ec302844da2f39e1c1344..1d1b47ee78d7e2bf6e459af783c68dd9f925c158 100644
--- a/src/app/core/dspace-rest-v2/mocks/mock-response-map.ts
+++ b/src/app/shared/mocks/dspace-rest-v2/mocks/mock-response-map.ts
@@ -1,5 +1,5 @@
 import { InjectionToken } from '@angular/core';
-import mockSubmissionResponse from '../mocks/mock-submission-response.json';
+import mockSubmissionResponse from './mock-submission-response.json';
 
 export class MockResponseMap extends Map<string, any> {};
 
diff --git a/src/app/core/dspace-rest-v2/mocks/mock-submission-response.json b/src/app/shared/mocks/dspace-rest-v2/mocks/mock-submission-response.json
similarity index 100%
rename from src/app/core/dspace-rest-v2/mocks/mock-submission-response.json
rename to src/app/shared/mocks/dspace-rest-v2/mocks/mock-submission-response.json
diff --git a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html
index 4455cedeb93b9195636ee672c808f438da63a45c..92d85d03f452c13377c58d02c008eec8241f985c 100644
--- a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html
+++ b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html
@@ -3,10 +3,10 @@
            [name]="'checkbox' + index"
            [id]="'object' + index"
            [ngModel]="selected$ | async"
-           (ngModelChange)="selectCheckbox($event, object)">
+           (ngModelChange)="selectCheckbox($event)">
     <input *ngIf="!selectionConfig.repeatable" class="form-check-input" type="radio"
            [name]="'radio' + index"
            [id]="'object' + index"
            [checked]="selected$ | async"
-           (click)="selectRadio(!checked, object)">
-</ng-container>
\ No newline at end of file
+           (click)="selectRadio(!checked)">
+</ng-container>
diff --git a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.spec.ts b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..25cf6b15f0eff998ffd40cb552c32e451b5e4fcd
--- /dev/null
+++ b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.spec.ts
@@ -0,0 +1,91 @@
+import { ComponentFixture, TestBed, async, tick, fakeAsync } from '@angular/core/testing';
+import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
+import { SelectableListService } from '../../../object-list/selectable-list/selectable-list.service';
+import { SelectableListItemControlComponent } from './selectable-list-item-control.component';
+import { Item } from '../../../../core/shared/item.model';
+import { FormsModule } from '@angular/forms';
+import { VarDirective } from '../../../utils/var.directive';
+import { of as observableOf } from 'rxjs';
+import { ListableObject } from '../listable-object.model';
+
+describe('SelectableListItemControlComponent', () => {
+  let comp: SelectableListItemControlComponent;
+  let fixture: ComponentFixture<SelectableListItemControlComponent>;
+  let de: DebugElement;
+  let el: HTMLElement;
+  let object;
+  let otherObject;
+  let selectionConfig;
+  let listId;
+  let index;
+  let selectionService;
+  let selection: ListableObject[];
+  let uuid1: string;
+  let uuid2: string;
+
+  function init() {
+    uuid1 = '0beb44f8-d2ed-459a-a1e7-ffbe059089a9';
+    uuid2 = 'e1dc80aa-c269-4aa5-b6bd-008d98056247';
+    listId = 'Test List ID';
+    object = Object.assign(new Item(), {uuid: uuid1});
+    otherObject = Object.assign(new Item(), {uuid: uuid2});
+    selectionConfig = {repeatable: false, listId};
+    index = 0;
+    selection = [otherObject];
+    selectionService = jasmine.createSpyObj('selectionService', {
+        selectSingle: jasmine.createSpy('selectSingle'),
+        deselectSingle: jasmine.createSpy('deselectSingle'),
+        isObjectSelected: observableOf(true),
+        getSelectableList: observableOf({ selection })
+      }
+    );
+  }
+
+  beforeEach(async(() => {
+    init();
+    TestBed.configureTestingModule({
+      declarations: [SelectableListItemControlComponent, VarDirective],
+      imports: [FormsModule],
+      providers: [
+        {
+          provide: SelectableListService,
+          useValue: selectionService
+        }
+      ],
+      schemas: [NO_ERRORS_SCHEMA]
+    }).compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SelectableListItemControlComponent);
+    comp = fixture.componentInstance; // SelectableListItemControlComponent test instance
+    de = fixture.debugElement;
+    el = de.nativeElement;
+    comp.object = object;
+    comp.selectionConfig = selectionConfig;
+    comp.index = index;
+    fixture.detectChanges();
+  });
+
+  it('should call deselectSingle on the service when the object when selectCheckbox is called with value false', () => {
+    comp.selectCheckbox(false);
+    expect(selectionService.deselectSingle).toHaveBeenCalledWith(listId, object);
+  });
+
+  it('should call selectSingle on the service when the object when selectCheckbox is called with value false', () => {
+    comp.selectCheckbox(true);
+    expect(selectionService.selectSingle).toHaveBeenCalledWith(listId, object);
+  });
+
+  it('should call selectSingle on the service when the object when selectRadio is called with value true and deselect all others in the selection', () => {
+    comp.selectRadio(true );
+    expect(selectionService.deselectSingle).toHaveBeenCalledWith(listId, selection[0]);
+    expect(selectionService.selectSingle).toHaveBeenCalledWith(listId, object);
+  });
+
+  it('should not call selectSingle on the service when the object when selectRadio is called with value false and not deselect all others in the selection', () => {
+    comp.selectRadio(false );
+    expect(selectionService.deselectSingle).not.toHaveBeenCalledWith(listId, selection[0]);
+    expect(selectionService.selectSingle).not.toHaveBeenCalledWith(listId, object);
+  });
+});
diff --git a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.ts b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.ts
index 735f06fe5ff0737b0269de50fc8b7383d8afe5e8..d1536c56e6f8e46d0232dd49c2afb8f02a7022c9 100644
--- a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.ts
+++ b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.ts
@@ -49,29 +49,30 @@ export class SelectableListItemControlComponent implements OnInit {
     })
   }
 
-  selectCheckbox(value: boolean, object: ListableObject) {
+  selectCheckbox(value: boolean) {
     if (value) {
-      this.selectionService.selectSingle(this.selectionConfig.listId, object);
+      this.selectionService.selectSingle(this.selectionConfig.listId, this.object);
     } else {
-      this.selectionService.deselectSingle(this.selectionConfig.listId, object);
+      this.selectionService.deselectSingle(this.selectionConfig.listId, this.object);
     }
   }
 
-  selectRadio(value: boolean, object: ListableObject) {
-    const selected$ = this.selectionService.getSelectableList(this.selectionConfig.listId);
-    selected$.pipe(
-      take(1),
-      map((selected) => selected ? selected.selection : [])
-    ).subscribe((selection) => {
-      // First deselect any existing selections, this is a radio button
-      selection.forEach((selectedObject) => {
-        this.selectionService.deselectSingle(this.selectionConfig.listId, selectedObject);
-        this.deselectObject.emit(selectedObject);
-      });
-      if (value) {
-        this.selectionService.selectSingle(this.selectionConfig.listId, object);
-        this.selectObject.emit(object);
-      }
-    });
+  selectRadio(value: boolean) {
+    if (value) {
+      const selected$ = this.selectionService.getSelectableList(this.selectionConfig.listId);
+      selected$.pipe(
+        take(1),
+        map((selected) => selected ? selected.selection : [])
+      ).subscribe((selection) => {
+          // First deselect any existing selections, this is a radio button
+          selection.forEach((selectedObject) => {
+            this.selectionService.deselectSingle(this.selectionConfig.listId, selectedObject);
+            this.deselectObject.emit(selectedObject);
+          });
+          this.selectionService.selectSingle(this.selectionConfig.listId, this.object);
+          this.selectObject.emit(this.object);
+        }
+      );
+    }
   }
 }
diff --git a/src/app/shared/object-list/selectable-list/selectable-list.actions.ts b/src/app/shared/object-list/selectable-list/selectable-list.actions.ts
index 7b868c99ff753793c77de9117ad6ad4c3b345d93..3dedf7e6a2363695c81799df3f4ed4169edab015 100644
--- a/src/app/shared/object-list/selectable-list/selectable-list.actions.ts
+++ b/src/app/shared/object-list/selectable-list/selectable-list.actions.ts
@@ -19,6 +19,9 @@ export const SelectableListActionTypes = {
   DESELECT_ALL: type('dspace/selectable-lists/DESELECT_ALL')
 };
 
+/**
+ * Abstract action class for actions on selectable lists
+ */
 /* tslint:disable:max-classes-per-file */
 export abstract class SelectableListAction implements Action {
   // tslint:disable-next-line:no-shadowed-variable
@@ -27,7 +30,7 @@ export abstract class SelectableListAction implements Action {
 }
 
 /**
- * Used to select an item in a the selectable list
+ * Action to select objects in a the selectable list
  */
 export class SelectableListSelectAction extends SelectableListAction {
   payload: ListableObject[];
@@ -37,7 +40,9 @@ export class SelectableListSelectAction extends SelectableListAction {
     this.payload = objects;
   }
 }
-
+/**
+ * Action to select a single object in a the selectable list
+ */
 export class SelectableListSelectSingleAction extends SelectableListAction {
   payload: {
     object: ListableObject,
@@ -49,6 +54,9 @@ export class SelectableListSelectSingleAction extends SelectableListAction {
   }
 }
 
+/**
+ * Action to deselect objects in a the selectable list
+ */
 export class SelectableListDeselectSingleAction extends SelectableListAction {
   payload: ListableObject;
 
@@ -58,6 +66,9 @@ export class SelectableListDeselectSingleAction extends SelectableListAction {
   }
 }
 
+/**
+ * Action to deselect a single object in a the selectable list
+ */
 export class SelectableListDeselectAction extends SelectableListAction {
   payload: ListableObject[];
 
@@ -67,6 +78,9 @@ export class SelectableListDeselectAction extends SelectableListAction {
   }
 }
 
+/**
+ * Action to set a new or overwrite an existing selection
+ */
 export class SelectableListSetSelectionAction extends SelectableListAction {
   payload: ListableObject[];
 
@@ -76,6 +90,9 @@ export class SelectableListSetSelectionAction extends SelectableListAction {
   }
 }
 
+/**
+ * Action to deselect all currently selected objects
+ */
 export class SelectableListDeselectAllAction extends SelectableListAction {
   constructor(id: string) {
     super(SelectableListActionTypes.DESELECT_ALL, id);
diff --git a/src/app/shared/object-list/selectable-list/selectable-list.reducer.spec.ts b/src/app/shared/object-list/selectable-list/selectable-list.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..29b60cdc02c99e5e3d62687954bf7dd26b7c2d5b
--- /dev/null
+++ b/src/app/shared/object-list/selectable-list/selectable-list.reducer.spec.ts
@@ -0,0 +1,112 @@
+import {
+  SelectableListAction,
+  SelectableListDeselectAction, SelectableListDeselectAllAction,
+  SelectableListDeselectSingleAction,
+  SelectableListSelectAction,
+  SelectableListSelectSingleAction,
+  SelectableListSetSelectionAction
+} from './selectable-list.actions';
+import { selectableListReducer } from './selectable-list.reducer';
+import { ListableObject } from '../../object-collection/shared/listable-object.model';
+import { hasValue } from '../../empty.util';
+
+// tslint:disable:max-classes-per-file
+class SelectableObject extends ListableObject {
+  constructor(private value: string) {
+    super();
+  }
+
+  equals(other: SelectableObject): boolean {
+    return hasValue(this.value) && hasValue(other.value) && this.value === other.value;
+  }
+
+  getRenderTypes() {
+    return ['selectable'];
+  }
+}
+
+class NullAction extends SelectableListAction {
+  type = null;
+
+  constructor() {
+    super(undefined, undefined);
+  }
+}
+
+// tslint:enable:max-classes-per-file
+const listID1 = 'id1';
+const listID2 = 'id2';
+const value1 = 'Selected object';
+const value2 = 'Another selected object';
+const value3 = 'Selection';
+const value4 = 'Selected object numero 4';
+
+const selected1 = new SelectableObject(value1);
+const selected2 = new SelectableObject(value2);
+const selected3 = new SelectableObject(value3);
+const selected4 = new SelectableObject(value4);
+const testState = { [listID1]: { id: listID1, selection: [selected1, selected2] } };
+
+describe('selectableListReducer', () => {
+
+  it('should return the current state when no valid actions have been made', () => {
+    const state = {};
+    state[listID1] = {};
+    state[listID1] = { id: listID1, selection: [selected1, selected2] };
+    const action = new NullAction();
+    const newState = selectableListReducer(state, action);
+
+    expect(newState).toEqual(state);
+  });
+
+  it('should start with an empty object', () => {
+    const state = {};
+    const action = new NullAction();
+    const newState = selectableListReducer(undefined, action);
+
+    expect(newState).toEqual(state);
+  });
+
+  it('should add the payload to the existing list in response to the SELECT action for the given id', () => {
+    const action = new SelectableListSelectAction(listID1, [selected3, selected4]);
+    const newState = selectableListReducer(testState, action);
+
+    expect(newState[listID1].selection).toEqual([selected1, selected2, selected3, selected4]);
+  });
+
+  it('should add the payload to the existing list in response to the SELECT_SINGLE action for the given id', () => {
+    const action = new SelectableListSelectSingleAction(listID1, selected4);
+    const newState = selectableListReducer(testState, action);
+
+    expect(newState[listID1].selection).toEqual([selected1, selected2, selected4]);
+  });
+
+  it('should remove the payload from the existing list in response to the DESELECT action for the given id', () => {
+    const action = new SelectableListDeselectAction(listID1, [selected1, selected2]);
+    const newState = selectableListReducer(testState, action);
+
+    expect(newState[listID1].selection).toEqual([]);
+  });
+
+  it('should remove the payload from the existing list in response to the DESELECT_SINGLE action for the given id', () => {
+    const action = new SelectableListDeselectSingleAction(listID1, selected2);
+    const newState = selectableListReducer(testState, action);
+
+    expect(newState[listID1].selection).toEqual([selected1]);
+  });
+
+  it('should set the list to the payload in response to the SET_SELECTION action for the given id', () => {
+    const action = new SelectableListSetSelectionAction(listID2, [selected2, selected4]);
+    const newState = selectableListReducer(testState, action);
+
+    expect(newState[listID1].selection).toEqual(testState[listID1].selection);
+    expect(newState[listID2].selection).toEqual([selected2, selected4]);
+  });
+
+  it('should remove the payload from the existing list in response to the DESELECT action for the given id', () => {
+    const action = new SelectableListDeselectAllAction(listID1);
+    const newState = selectableListReducer(testState, action);
+
+    expect(newState[listID1].selection).toEqual([]);
+  });
+});
diff --git a/src/app/shared/object-list/selectable-list/selectable-list.reducer.ts b/src/app/shared/object-list/selectable-list/selectable-list.reducer.ts
index 927e20ff21a80316be4af852653f0797b0ecd8a5..4c7251e563d37488ce305b0b51de14f8d44cfa0f 100644
--- a/src/app/shared/object-list/selectable-list/selectable-list.reducer.ts
+++ b/src/app/shared/object-list/selectable-list/selectable-list.reducer.ts
@@ -63,12 +63,22 @@ export function selectableListReducer(state: SelectableListsState = {}, action:
   }
 }
 
+/**
+ * Adds multiple objects to the existing selection state
+ * @param state The current state
+ * @param action The action to perform
+ */
 function select(state: SelectableListState, action: SelectableListSelectAction) {
   const filteredNewObjects = action.payload.filter((object) => !isObjectInSelection(state.selection, object));
   const newSelection = [...state.selection, ...filteredNewObjects];
   return Object.assign({}, state, { selection: newSelection });
 }
 
+/**
+ * Adds a single object to the existing selection state
+ * @param state The current state
+ * @param action The action to perform
+ */
 function selectSingle(state: SelectableListState, action: SelectableListSelectSingleAction) {
   let newSelection = state.selection;
   if (!isObjectInSelection(state.selection, action.payload.object)) {
@@ -77,11 +87,21 @@ function selectSingle(state: SelectableListState, action: SelectableListSelectSi
   return Object.assign({}, state, { selection: newSelection });
 }
 
+/**
+ * Removes multiple objects in the existing selection state
+ * @param state The current state
+ * @param action The action to perform
+ */
 function deselect(state: SelectableListState, action: SelectableListDeselectAction) {
   const newSelection = state.selection.filter((selected) => hasNoValue(action.payload.find((object) => object.equals(selected))));
   return Object.assign({}, state, { selection: newSelection });
 }
 
+/** Removes a single object from the existing selection state
+ *
+ * @param state The current state
+ * @param action The action to perform
+ */
 function deselectSingle(state: SelectableListState, action: SelectableListDeselectSingleAction) {
   const newSelection = state.selection.filter((selected) => {
     return !selected.equals(action.payload);
@@ -89,14 +109,29 @@ function deselectSingle(state: SelectableListState, action: SelectableListDesele
   return Object.assign({}, state, { selection: newSelection });
 }
 
+/**
+ * Sets the selection state of the list
+ * @param state The current state
+ * @param action The action to perform
+ */
 function setList(state: SelectableListState, action: SelectableListSetSelectionAction) {
   return Object.assign({}, state, { selection: action.payload });
 }
 
+/**
+ * Clears the selection
+ * @param state The current state
+ * @param action The action to perform
+ */
 function clearSelection(id: string) {
   return { id: id, selection: [] };
 }
 
+/**
+ * Checks whether the object is in currently in the selection
+ * @param state The current state
+ * @param action The action to perform
+ */
 function isObjectInSelection(selection: ListableObject[], object: ListableObject) {
   return selection.findIndex((selected) => selected.equals(object)) >= 0
 }
diff --git a/src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts b/src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7699541fe352e8fecf378879eda8adbf45c7e65f
--- /dev/null
+++ b/src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts
@@ -0,0 +1,98 @@
+import { Store } from '@ngrx/store';
+import { async, TestBed } from '@angular/core/testing';
+import { SelectableListService } from './selectable-list.service';
+import { SelectableListsState } from './selectable-list.reducer';
+import { ListableObject } from '../../object-collection/shared/listable-object.model';
+import { hasValue } from '../../empty.util';
+import { SelectableListDeselectAction, SelectableListDeselectSingleAction, SelectableListSelectAction, SelectableListSelectSingleAction } from './selectable-list.actions';
+
+class SelectableObject extends ListableObject {
+  constructor(private value: string) {
+    super();
+  }
+
+  equals(other: SelectableObject): boolean {
+    return hasValue(this.value) && hasValue(other.value) && this.value === other.value;
+  }
+
+  getRenderTypes() {
+    return ['selectable'];
+  }
+}
+
+describe('SelectableListService', () => {
+  const listID1 = 'id1';
+  const value1 = 'Selected object';
+  const value2 = 'Another selected object';
+  const value3 = 'Selection';
+  const value4 = 'Selected object numero 4';
+
+  const selected1 = new SelectableObject(value1);
+  const selected2 = new SelectableObject(value2);
+  const selected3 = new SelectableObject(value3);
+  const selected4 = new SelectableObject(value4);
+
+  let service: SelectableListService;
+  const store: Store<SelectableListsState> = jasmine.createSpyObj('store', {
+    /* tslint:disable:no-empty */
+    dispatch: {},
+    /* tslint:enable:no-empty */
+  });
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+
+      providers: [
+        {
+          provide: Store, useValue: store
+        }
+      ]
+    }).compileComponents();
+  }));
+
+  beforeEach(() => {
+    service = new SelectableListService(store);
+  });
+
+  describe('when the selectSingle method is triggered', () => {
+    beforeEach(() => {
+      service.selectSingle(listID1, selected3);
+    });
+
+    it('SelectableListSelectSingleAction should be dispatched to the store', () => {
+      expect(store.dispatch).toHaveBeenCalledWith(new SelectableListSelectSingleAction(listID1, selected3));
+    });
+
+  });
+
+  describe('when the select method is triggered', () => {
+    beforeEach(() => {
+      service.select(listID1, [selected1, selected4]);
+    });
+
+    it('SelectableListSelectAction should be dispatched to the store', () => {
+      expect(store.dispatch).toHaveBeenCalledWith(new SelectableListSelectAction(listID1, [selected1, selected4]));
+    });
+  });
+
+  describe('when the deselectSingle method is triggered', () => {
+    beforeEach(() => {
+      service.deselectSingle(listID1, selected4);
+    });
+
+    it('SelectableListDeselectSingleAction should be dispatched to the store', () => {
+      expect(store.dispatch).toHaveBeenCalledWith(new SelectableListDeselectSingleAction(listID1, selected4));
+    });
+
+  });
+
+  describe('when the deselect method is triggered', () => {
+    beforeEach(() => {
+      service.deselect(listID1, [selected2, selected4]);
+    });
+
+    it('SelectableListDeselectAction should be dispatched to the store', () => {
+      expect(store.dispatch).toHaveBeenCalledWith(new SelectableListDeselectAction(listID1, [selected2, selected4]));
+    });
+  });
+
+});
diff --git a/src/app/shared/search-form/search-form.component.spec.ts b/src/app/shared/search-form/search-form.component.spec.ts
index 03081e909e0fcba012d41e25a8af1247775db984..74ed4bb913e7179523e2d64401ed387468dceab2 100644
--- a/src/app/shared/search-form/search-form.component.spec.ts
+++ b/src/app/shared/search-form/search-form.component.spec.ts
@@ -3,7 +3,6 @@ import { By } from '@angular/platform-browser';
 import { DebugElement } from '@angular/core';
 import { SearchFormComponent } from './search-form.component';
 import { FormsModule } from '@angular/forms';
-import { ResourceType } from '../../core/shared/resource-type';
 import { RouterTestingModule } from '@angular/router/testing';
 import { Community } from '../../core/shared/community.model';
 import { TranslateModule } from '@ngx-translate/core';
diff --git a/src/app/shared/utils/relation-query.utils.spec.ts b/src/app/shared/utils/relation-query.utils.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f70e90442281c6ff4f4fb26cd2bdf582128c87e4
--- /dev/null
+++ b/src/app/shared/utils/relation-query.utils.spec.ts
@@ -0,0 +1,18 @@
+import { getFilterByRelation, getQueryByRelations } from './relation-query.utils';
+
+describe('Relation Query Utils', () => {
+  const relationtype = 'isAuthorOfPublication';
+  const itemUUID = 'a7939af0-36ad-430d-af09-7be8b0a4dadd';
+  describe('getQueryByRelations', () => {
+    it('Should return the correct query based on relationtype and uuid', () => {
+      const result = getQueryByRelations(relationtype, itemUUID);
+      expect(result).toEqual('query=relation.isAuthorOfPublication:a7939af0-36ad-430d-af09-7be8b0a4dadd');
+    });
+  });
+  describe('getFilterByRelation', () => {
+    it('Should return the correct query based on relationtype and uuid', () => {
+      const result = getFilterByRelation(relationtype, itemUUID);
+      expect(result).toEqual('f.isAuthorOfPublication=a7939af0-36ad-430d-af09-7be8b0a4dadd');
+    });
+  });
+});
diff --git a/src/app/shared/utils/route.utils.spec.ts b/src/app/shared/utils/route.utils.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..610fd8756d6bb3afd4582c9f3b704f5035bf16b0
--- /dev/null
+++ b/src/app/shared/utils/route.utils.spec.ts
@@ -0,0 +1,22 @@
+import { currentPath } from './route.utils';
+
+describe('Route Utils', () => {
+    const urlTree = {
+      root: {
+        children: {
+          primary: {
+            segments: [
+              { path: 'test' },
+              { path: 'path' }
+            ]
+          }
+
+        }
+      }
+    };
+    const router = { parseUrl: () => urlTree } as any;
+    it('Should return the correct current path based on the router', () => {
+      const result = currentPath(router);
+      expect(result).toEqual('/test/path');
+    });
+  });
diff --git a/src/app/statistics/statistics.service.ts b/src/app/statistics/statistics.service.ts
index 0c16dc475543ce2ae0b4e86969a6cb95b2f03fbc..004e013164ba20b19a5d2b9773ac8d36afe89830 100644
--- a/src/app/statistics/statistics.service.ts
+++ b/src/app/statistics/statistics.service.ts
@@ -66,13 +66,13 @@ export class StatisticsService {
       },
     };
     if (hasValue(searchOptions.configuration)) {
-      Object.assign(body, {configuration: searchOptions.configuration})
+      Object.assign(body, { configuration: searchOptions.configuration })
     }
     if (hasValue(searchOptions.dsoType)) {
-      Object.assign(body, {dsoType: searchOptions.dsoType.toLowerCase()})
+      Object.assign(body, { dsoType: searchOptions.dsoType.toLowerCase() })
     }
     if (hasValue(searchOptions.scope)) {
-      Object.assign(body, {scope: searchOptions.scope})
+      Object.assign(body, { scope: searchOptions.scope })
     }
     if (isNotEmpty(filters)) {
       const bodyFilters = [];
@@ -85,7 +85,7 @@ export class StatisticsService {
           label: filter.label
         })
       }
-      Object.assign(body, {appliedFilters: bodyFilters})
+      Object.assign(body, { appliedFilters: bodyFilters })
     }
     this.sendEvent('/statistics/searchevents', body);
   }
diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts
index 0bd24cc3049da618f774cdb3262ee5006fdaa68c..88bc4904d3a62d2623fc85f272ce750451c31110 100644
--- a/src/app/submission/form/collection/submission-form-collection.component.ts
+++ b/src/app/submission/form/collection/submission-form-collection.component.ts
@@ -16,7 +16,7 @@ import { SubmissionService } from '../../submission.service';
 import { SubmissionObject } from '../../../core/submission/models/submission-object.model';
 import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service';
 import { CollectionDataService } from '../../../core/data/collection-data.service';
-import { FindAllOptions } from '../../../core/data/request.models';
+import { FindListOptions } from '../../../core/data/request.models';
 
 /**
  * An interface to represent a collection entry
@@ -185,7 +185,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
         map((collectionRD: RemoteData<Collection>) => collectionRD.payload.name)
       );
 
-      const findOptions: FindAllOptions = {
+      const findOptions: FindListOptions = {
         elementsPerPage: 1000
       };
 
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
index 99d92d2af812da515a4a05a785bdb1ed3054be7d..907f70b9411ac6feac7cafb58b47f93c0fad5c6c 100644
--- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
+++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
@@ -4,7 +4,7 @@
 
             <div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
                 <ds-metadata-field-wrapper>
-                    <ds-thumbnail [thumbnail]="item.getThumbnail() | async"></ds-thumbnail>
+                    <ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
                 </ds-metadata-field-wrapper>
                 <div>
                     <a class="btn btn-secondary"
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
index 1b8a283da9fed56fefd81a92b9fd0c53d5087aa7..089511804edad61a39a36d2b8ed8d14335c3e411 100644
--- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
+++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
@@ -61,7 +61,10 @@
     <div class="container search-container">
         <h3 class="h2">{{"item.page.journal.search.title" | translate}}</h3>
     </div>
-    <ds-related-entities-search [item]="object"
-                                [relationType]="'isJournalOfPublication'">
-    </ds-related-entities-search>
+    <ds-tabbed-related-entities-search  [item]="object"
+                                        [relationTypes]="[{
+                                            label: 'isJournalOfPublication',
+                                            filter: 'isJournalOfPublication'
+                                          }]">
+    </ds-tabbed-related-entities-search>
 </div>
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
similarity index 79%
rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html
rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
index 15529a1bd537b76dc2a2f7f79cea990f7fa9b97b..ee78d9c653696608464157ec202e26e49366faf2 100644
--- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
@@ -53,18 +53,6 @@
 <div class="relationships-item-page">
     <div class="container">
         <div class="row">
-            <ds-related-items
-              class="col-12 col-md-4"
-              [parentItem]="object"
-              [relationType]="'isPersonOfOrgUnit'"
-              [label]="'relationships.isPersonOf' | translate">
-            </ds-related-items>
-            <ds-related-items
-              class="col-12 col-md-4"
-              [parentItem]="object"
-              [relationType]="'isProjectOfOrgUnit'"
-              [label]="'relationships.isProjectOf' | translate">
-            </ds-related-items>
             <ds-related-items
               class="col-12 col-md-4"
               [parentItem]="object"
@@ -74,3 +62,20 @@
         </div>
     </div>
 </div>
+<div class="container">
+  <div class="row">
+    <ds-tabbed-related-entities-search  class="w-100"
+                                        [item]="object"
+                                        [relationTypes]="[{
+                                            label: 'isOrgUnitOfPerson',
+                                            filter: 'isOrgUnitOfPerson',
+                                            configuration: 'person'
+                                          },
+                                          {
+                                            label: 'isOrgUnitOfProject',
+                                            filter: 'isOrgUnitOfProject',
+                                            configuration: 'project'
+                                          }]">
+    </ds-tabbed-related-entities-search>
+  </div>
+</div>
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss
similarity index 86%
rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss
rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss
index 54651aede0f5c7487ed59528d20b1a3d1fb84af5..4a1d2516da01be6d5400d963ec22fa04f9ce821b 100644
--- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss
@@ -1,4 +1,4 @@
-@import 'src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss';
+@import 'src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss';
 
 :host {
     > * {
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
index bb5cb1b7871b340567f8737b8db27110f0320eec..1679f9354da9a1b8488a57c8b0c08df4ab4b5f17 100644
--- a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
@@ -79,7 +79,10 @@
     <div class="container search-container">
         <h3 class="h2">{{"item.page.person.search.title" | translate}}</h3>
     </div>
-<ds-related-entities-search [item]="object"
-                            [relationType]="'isAuthorOfPublication'">
-</ds-related-entities-search>
+<ds-tabbed-related-entities-search  [item]="object"
+                                    [relationTypes]="[{
+                                        label: 'isAuthorOfPublication',
+                                        filter: 'isAuthorOfPublication'
+                                      }]">
+</ds-tabbed-related-entities-search>
 </div>
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js
index 028815d958b8e9b987c057177c66ea52348524c0..e63ae024ed0928067e74dfa6fb45b0b9d6b2e801 100644
--- a/webpack/webpack.common.js
+++ b/webpack/webpack.common.js
@@ -15,6 +15,9 @@ module.exports = (env) => {
   let copyWebpackOptions = [{
     from: path.join(__dirname, '..', 'node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'),
     to: path.join('assets', 'fonts')
+  }, {
+    from: path.join(__dirname, '..', 'resources', 'fonts'),
+    to: path.join('assets', 'fonts')
   }, {
     from: path.join(__dirname, '..', 'resources', 'images'),
     to: path.join('assets', 'images')
@@ -24,6 +27,15 @@ module.exports = (env) => {
   }
   ];
 
+  const themeFonts = path.join(themePath, 'resources', 'fonts');
+  if(theme && fs.existsSync(themeFonts)) {
+    copyWebpackOptions.push({
+      from: themeFonts,
+      to: path.join('assets', 'fonts')  ,
+      force: true,
+    });
+  }
+
   const themeImages = path.join(themePath, 'resources', 'images');
   if(theme && fs.existsSync(themeImages)) {
     copyWebpackOptions.push({
@@ -107,12 +119,6 @@ module.exports = (env) => {
                                 sourceMap: true
                             }
                         },
-                        {
-                            loader: 'resolve-url-loader',
-                            options: {
-                                sourceMap: true
-                            }
-                        },
                         {
                             loader: 'sass-loader',
                             options: {
@@ -120,6 +126,12 @@ module.exports = (env) => {
                                 includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
                             }
                         },
+                        {
+                          loader: 'resolve-url-loader',
+                          options: {
+                            sourceMap: true
+                          }
+                        },
                         {
                             loader: 'sass-resources-loader',
                             options: {
@@ -145,23 +157,23 @@ module.exports = (env) => {
                                 sourceMap: true
                             }
                         },
-                        {
-                            loader: 'resolve-url-loader',
-                            options: {
-                                sourceMap: true
-                            }
-                        },
                         {
                             loader: 'sass-loader',
                             options: {
                                 sourceMap: true,
                                 includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
                             }
-                        }
+                          },
+                        {
+                            loader: 'resolve-url-loader',
+                            options: {
+                                sourceMap: true
+                            }
+                        },
                     ]
                 },
                 {
-                    test: /\.html$/,
+                    test: /\.(html|eot|ttf|otf|svg|woff|woff2)$/,
                     loader: 'raw-loader'
                 }
             ]
diff --git a/webpack/webpack.test.js b/webpack/webpack.test.js
index 83e6e44e792e9fef1ed4e2a6f1aead2bcd9097a2..de53de31c4f7bb0e6c65b745d637e8cd15d57266 100644
--- a/webpack/webpack.test.js
+++ b/webpack/webpack.test.js
@@ -161,16 +161,16 @@ module.exports = function (env) {
                             }
                         },
                         {
-                            loader: 'resolve-url-loader',
+                            loader: 'sass-loader',
                             options: {
-                                sourceMap: true
+                                sourceMap: true,
+                                includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
                             }
                         },
                         {
-                            loader: 'sass-loader',
+                            loader: 'resolve-url-loader',
                             options: {
-                                sourceMap: true,
-                                includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
+                                sourceMap: true
                             }
                         },
                         {
@@ -199,18 +199,18 @@ module.exports = function (env) {
                             }
                         },
                         {
-                            loader: 'resolve-url-loader',
+                            loader: 'sass-loader',
                             options: {
-                                sourceMap: true
+                                sourceMap: true,
+                                includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
                             }
                         },
                         {
-                            loader: 'sass-loader',
+                            loader: 'resolve-url-loader',
                             options: {
-                                sourceMap: true,
-                                includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
+                                sourceMap: true
                             }
-                        }
+                        },
                     ]
                 },
 
diff --git a/yarn.lock b/yarn.lock
index 884f820c1d1edd3fdc6dbfe9b014e9bbe0f7ee89..98b39370e444ffd1fd376c4623c9e79e2bc6ae39 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2102,15 +2102,14 @@ cliui@^5.0.0:
     strip-ansi "^5.2.0"
     wrap-ansi "^5.1.0"
 
-clone-deep@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713"
-  integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==
+clone-deep@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
+  integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
   dependencies:
-    for-own "^1.0.0"
     is-plain-object "^2.0.4"
-    kind-of "^6.0.0"
-    shallow-clone "^1.0.0"
+    kind-of "^6.0.2"
+    shallow-clone "^3.0.0"
 
 clone-stats@^0.0.1:
   version "0.0.1"
@@ -4130,11 +4129,6 @@ font-awesome@4.7.0:
   resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
   integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=
 
-for-in@^0.1.3:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
-  integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=
-
 for-in@^1.0.1, for-in@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -4147,13 +4141,6 @@ for-own@^0.1.4:
   dependencies:
     for-in "^1.0.1"
 
-for-own@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b"
-  integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=
-  dependencies:
-    for-in "^1.0.1"
-
 forever-agent@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -6157,7 +6144,7 @@ loader-utils@^0.2.12, loader-utils@^0.2.15, loader-utils@^0.2.16:
     json5 "^0.5.0"
     object-assign "^4.0.1"
 
-loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0:
+loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
   integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=
@@ -6166,7 +6153,7 @@ loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1
     emojis-list "^2.0.0"
     json5 "^0.5.0"
 
-loader-utils@^1.0.4:
+loader-utils@^1.0.1, loader-utils@^1.0.4:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
   dependencies:
@@ -6380,11 +6367,6 @@ lodash.startswith@^4.2.1:
   resolved "https://registry.yarnpkg.com/lodash.startswith/-/lodash.startswith-4.2.1.tgz#c598c4adce188a27e53145731cdc6c0e7177600c"
   integrity sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=
 
-lodash.tail@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
-  integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
-
 lodash.template@^3.0.0:
   version "3.6.2"
   resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f"
@@ -6858,14 +6840,6 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mixin-object@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
-  integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=
-  dependencies:
-    for-in "^0.1.3"
-    is-extendable "^0.1.1"
-
 mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@@ -9673,17 +9647,16 @@ sass-graph@^2.2.4:
     scss-tokenizer "^0.2.3"
     yargs "^7.0.0"
 
-sass-loader@7.1.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d"
-  integrity sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==
+sass-loader@^7.1.0:
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.3.1.tgz#a5bf68a04bcea1c13ff842d747150f7ab7d0d23f"
+  integrity sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==
   dependencies:
-    clone-deep "^2.0.1"
+    clone-deep "^4.0.1"
     loader-utils "^1.0.1"
-    lodash.tail "^4.1.1"
     neo-async "^2.5.0"
-    pify "^3.0.0"
-    semver "^5.5.0"
+    pify "^4.0.1"
+    semver "^6.3.0"
 
 sass-resources-loader@^2.0.0:
   version "2.0.0"
@@ -9788,7 +9761,7 @@ semver-intersect@^1.1.2:
   dependencies:
     semver "^5.0.0"
 
-"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0:
+"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0:
   version "5.5.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
   integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==
@@ -9798,7 +9771,12 @@ semver@^5.0.1:
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
   integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
 
-semver@^6.1.1:
+semver@^5.5.0:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+semver@^6.1.1, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
@@ -9929,14 +9907,12 @@ sha.js@^2.4.0, sha.js@^2.4.8:
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
 
-shallow-clone@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571"
-  integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==
+shallow-clone@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+  integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
   dependencies:
-    is-extendable "^0.1.1"
-    kind-of "^5.0.0"
-    mixin-object "^2.0.1"
+    kind-of "^6.0.2"
 
 shebang-command@^1.2.0:
   version "1.2.0"