diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index fc7f58b6fb..c886aa655c 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -2,12 +2,11 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CollectionPageComponent } from './collection-page.component'; -import { NormalizedCollection } from '../core/cache/models/normalized-collection.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: CollectionPageComponent, pathMatch: 'full', data: { type: NormalizedCollection } } + { path: ':id', component: CollectionPageComponent, pathMatch: 'full' } ]) ] }) diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index 65bf708727..30d9c17fe3 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -7,6 +7,8 @@ import { } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; +import { PageInfo } from '../core/shared/page-info.model'; +import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; import { Collection } from '../core/shared/collection.model'; @@ -18,8 +20,8 @@ import { Item } from '../core/shared/item.model'; import { SortOptions, SortDirection } from '../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { hasValue, isNotEmpty, isUndefined } from '../shared/empty.util'; -import { PageInfo } from '../core/shared/page-info.model'; -import { Observable } from 'rxjs/Observable'; + +import { MetadataService } from '../core/metadata/metadata.service'; import { fadeIn, fadeInOut } from '../shared/animations/fade'; @@ -41,9 +43,12 @@ export class CollectionPageComponent implements OnInit, OnDestroy { private subs: Subscription[] = []; private collectionId: string; - constructor(private collectionDataService: CollectionDataService, - private itemDataService: ItemDataService, - private route: ActivatedRoute) { + constructor( + private collectionDataService: CollectionDataService, + private itemDataService: ItemDataService, + private metadata: MetadataService, + private route: ActivatedRoute + ) { this.paginationConfig = new PaginationComponentOptions(); this.paginationConfig.id = 'collection-page-pagination'; this.paginationConfig.pageSizeOptions = [4]; @@ -57,12 +62,13 @@ export class CollectionPageComponent implements OnInit, OnDestroy { Observable.combineLatest( this.route.params, this.route.queryParams, - (params, queryParams,) => { + (params, queryParams, ) => { return Object.assign({}, params, queryParams); }) .subscribe((params) => { this.collectionId = params.id; this.collectionData = this.collectionDataService.findById(this.collectionId); + this.metadata.processRemoteData(this.collectionData); this.subs.push(this.collectionData.payload.subscribe((collection) => this.logoData = collection.logo)); const page = +params.page || this.paginationConfig.currentPage; diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index e761717808..6fd5cc8cb5 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -2,12 +2,11 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommunityPageComponent } from './community-page.component'; -import { NormalizedCommunity } from '../core/cache/models/normalized-community.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: CommunityPageComponent, pathMatch: 'full', data: { type: NormalizedCommunity } } + { path: ':id', component: CommunityPageComponent, pathMatch: 'full' } ]) ] }) diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index 1295e14521..0cd94658be 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -9,6 +9,8 @@ import { RemoteData } from '../core/data/remote-data'; import { CommunityDataService } from '../core/data/community-data.service'; import { hasValue } from '../shared/empty.util'; +import { MetadataService } from '../core/metadata/metadata.service'; + import { fadeInOut } from '../shared/animations/fade'; @Component({ @@ -24,6 +26,7 @@ export class CommunityPageComponent implements OnInit, OnDestroy { constructor( private communityDataService: CommunityDataService, + private metadata: MetadataService, private route: ActivatedRoute ) { @@ -32,15 +35,13 @@ export class CommunityPageComponent implements OnInit, OnDestroy { ngOnInit(): void { this.route.params.subscribe((params: Params) => { this.communityData = this.communityDataService.findById(params.id); - this.subs.push(this.communityData.payload - .subscribe((community) => this.logoData = community.logo)); + this.metadata.processRemoteData(this.communityData); + this.subs.push(this.communityData.payload.subscribe((community) => this.logoData = community.logo)); }); } ngOnDestroy(): void { - this.subs - .filter((sub) => hasValue(sub)) - .forEach((sub) => sub.unsubscribe()); + this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/+item-page/full/full-item-page.component.ts b/src/app/+item-page/full/full-item-page.component.ts index 337c598021..270cf1fcae 100644 --- a/src/app/+item-page/full/full-item-page.component.ts +++ b/src/app/+item-page/full/full-item-page.component.ts @@ -1,15 +1,17 @@ import { Component, OnInit } from '@angular/core'; -import { animate, state, transition, trigger, style, keyframes } from '@angular/animations'; +import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { ItemPageComponent } from '../simple/item-page.component'; import { Metadatum } from '../../core/shared/metadatum.model'; import { ItemDataService } from '../../core/data/item-data.service'; -import { ActivatedRoute } from '@angular/router'; + import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; +import { MetadataService } from '../../core/metadata/metadata.service'; + import { fadeInOut } from '../../shared/animations/fade'; /** @@ -30,8 +32,8 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit { metadata: Observable; - constructor(route: ActivatedRoute, items: ItemDataService) { - super(route, items); + constructor(route: ActivatedRoute, items: ItemDataService, metadataService: MetadataService) { + super(route, items, metadataService); } /*** AoT inheritance fix, will hopefully be resolved in the near future **/ diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index e315b18a9c..9dca4d0f6e 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -3,13 +3,12 @@ import { RouterModule } from '@angular/router'; import { ItemPageComponent } from './simple/item-page.component'; import { FullItemPageComponent } from './full/full-item-page.component'; -import { NormalizedItem } from '../core/cache/models/normalized-item.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: ItemPageComponent, pathMatch: 'full', data: { type: NormalizedItem } }, - { path: ':id/full', component: FullItemPageComponent, data: { type: NormalizedItem } } + { path: ':id', component: ItemPageComponent, pathMatch: 'full' }, + { path: ':id/full', component: FullItemPageComponent } ]) ] }) diff --git a/src/app/+item-page/simple/item-page.component.ts b/src/app/+item-page/simple/item-page.component.ts index 8f1bd18b4e..ce9283e144 100644 --- a/src/app/+item-page/simple/item-page.component.ts +++ b/src/app/+item-page/simple/item-page.component.ts @@ -8,6 +8,8 @@ import { ItemDataService } from '../../core/data/item-data.service'; import { RemoteData } from '../../core/data/remote-data'; import { Bitstream } from '../../core/shared/bitstream.model'; +import { MetadataService } from '../../core/metadata/metadata.service'; + import { fadeInOut } from '../../shared/animations/fade'; /** @@ -31,7 +33,11 @@ export class ItemPageComponent implements OnInit { thumbnail: Observable; - constructor(private route: ActivatedRoute, private items: ItemDataService) { + constructor( + private route: ActivatedRoute, + private items: ItemDataService, + private metadataService: MetadataService + ) { } @@ -44,6 +50,7 @@ export class ItemPageComponent implements OnInit { initialize(params) { this.id = +params.id; this.item = this.items.findById(params.id); + this.metadataService.processRemoteData(this.item); this.thumbnail = this.item.payload.flatMap((i) => i.getThumbnail()); } diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 6f740b9f18..1258751f58 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -4,7 +4,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { Location, CommonModule } from '@angular/common'; import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { By, Meta, MetaDefinition, Title } from '@angular/platform-browser'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; @@ -19,17 +19,16 @@ import { CoreState } from '../core.reducers'; import { GlobalConfig } from '../../../config/global-config.interface'; import { ENV_CONFIG, GLOBAL_CONFIG } from '../../../config'; +import { ItemDataService } from '../data/item-data.service'; import { ObjectCacheService } from '../cache/object-cache.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RequestService } from '../data/request.service'; import { ResponseCacheService } from '../cache/response-cache.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; -import { NormalizedItem } from '../cache/models/normalized-item.model'; import { MockItem } from '../../shared/mocks/mock-item'; -import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; -import { MockRouter } from '../../shared/mocks/mock-router'; import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; /* tslint:disable:max-classes-per-file */ @@ -42,7 +41,13 @@ class TestComponent { } } -@Component({ template: '' }) class DummyItemComponent { } +@Component({ template: '' }) class DummyItemComponent { + constructor(private route: ActivatedRoute, private items: ItemDataService, private metadata: MetadataService) { + this.route.params.subscribe((params) => { + this.metadata.processRemoteData(this.items.findById(params.id)); + }); + } +} /* tslint:enable:max-classes-per-file */ describe('MetadataService', () => { @@ -58,6 +63,7 @@ describe('MetadataService', () => { let responseCacheService: ResponseCacheService; let requestService: RequestService; let remoteDataBuildService: RemoteDataBuildService; + let itemDataService: ItemDataService; let location: Location; let router: Router; @@ -88,8 +94,8 @@ describe('MetadataService', () => { } }), RouterTestingModule.withRoutes([ - { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } }, - { path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy component for testing!' } } + { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full' }, + { path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy item component for testing!' } } ]) ], declarations: [ @@ -104,12 +110,14 @@ describe('MetadataService', () => { { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, Meta, Title, + ItemDataService, MetadataService ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }); meta = TestBed.get(Meta); title = TestBed.get(Title); + itemDataService = TestBed.get(ItemDataService); metadataService = TestBed.get(MetadataService); envConfig = TestBed.get(GLOBAL_CONFIG); @@ -122,14 +130,8 @@ describe('MetadataService', () => { tagStore = metadataService.getTagStore(); }); - beforeEach(() => { - spyOn(objectCacheService, 'getByUUID').and.returnValue(Observable.create((observer) => { - observer.next(MockNormalizedItem); - })); - }); - it('items page should set meta tags', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(MockItem)); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(title.getTitle()).toEqual('Test PowerPoint Document'); @@ -142,7 +144,7 @@ describe('MetadataService', () => { })); it('items page should set meta tags as published Thesis', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Thesis'))); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(mockPublisher(mockType(MockItem, 'Thesis')))); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document'); @@ -152,14 +154,14 @@ describe('MetadataService', () => { })); it('items page should set meta tags as published Technical Report', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Technical Report'))); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(mockPublisher(mockType(MockItem, 'Technical Report')))); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(tagStore.get('citation_technical_report_institution')[0].content).toEqual('Mock Publisher'); })); it('other navigation should title and description', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(MockItem)); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(tagStore.size).toBeGreaterThan(0) @@ -168,9 +170,38 @@ describe('MetadataService', () => { expect(tagStore.size).toEqual(2); expect(title.getTitle()).toEqual('Dummy Title'); expect(tagStore.get('title')[0].content).toEqual('Dummy Title'); - expect(tagStore.get('description')[0].content).toEqual('This is a dummy component for testing!'); + expect(tagStore.get('description')[0].content).toEqual('This is a dummy item component for testing!'); })); + const mockRemoteData = (mockItem: Item): RemoteData => { + return new RemoteData( + Observable.create((observer) => { + observer.next(''); + }), + Observable.create((observer) => { + observer.next(false); + }), + Observable.create((observer) => { + observer.next(false); + }), + Observable.create((observer) => { + observer.next(true); + }), + Observable.create((observer) => { + observer.next(''); + }), + Observable.create((observer) => { + observer.next(200); + }), + Observable.create((observer) => { + observer.next({}); + }), + Observable.create((observer) => { + observer.next(MockItem); + }) + ); + } + const mockType = (mockItem: Item, type: string): Item => { const typedMockItem = Object.assign(new Item(), mockItem) as Item; for (const metadatum of typedMockItem.metadata) { diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 4090d2b484..32b002e721 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -17,13 +17,12 @@ import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../data/remote-data'; import { Bitstream } from '../shared/bitstream.model'; import { CacheableObject } from '../cache/object-cache.reducer'; import { DSpaceObject } from '../shared/dspace-object.model'; import { Item } from '../shared/item.model'; import { Metadatum } from '../shared/metadatum.model'; -import { ObjectCacheService } from '../cache/object-cache.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; @@ -38,8 +37,6 @@ export class MetadataService { constructor( private router: Router, - private objectCacheService: ObjectCacheService, - private remoteDataBuildService: RemoteDataBuildService, private translate: TranslateService, private meta: Meta, private title: Title, @@ -67,29 +64,29 @@ export class MetadataService { }); } + public processRemoteData(remoteData: RemoteData): void { + remoteData.payload.take(1).subscribe((dspaceObject: DSpaceObject) => { + if (!this.initialized) { + this.initialize(dspaceObject); + } + this.currentObject.next(dspaceObject); + }); + } + private processRouteChange(routeInfo: any): void { - if (routeInfo.params.value.id && routeInfo.data.value.type) { - this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) - .first().subscribe((normalizedObject: CacheableObject) => { - const dspaceObject = this.remoteDataBuildService.build(normalizedObject) as DSpaceObject; - if (!this.initialized) { - this.initialize(dspaceObject); - } - this.currentObject.next(dspaceObject); - }); - } else { + if (routeInfo.params.value.id === undefined) { this.clearMetaTags(); - if (routeInfo.data.value.title) { - this.translate.get(routeInfo.data.value.title).take(1).subscribe((translatedTitle: string) => { - this.addMetaTag('title', translatedTitle); - this.title.setTitle(translatedTitle); - }); - } - if (routeInfo.data.value.description) { - this.translate.get(routeInfo.data.value.description).take(1).subscribe((translatedDescription: string) => { - this.addMetaTag('description', translatedDescription); - }); - } + } + if (routeInfo.data.value.title) { + this.translate.get(routeInfo.data.value.title).take(1).subscribe((translatedTitle: string) => { + this.addMetaTag('title', translatedTitle); + this.title.setTitle(translatedTitle); + }); + } + if (routeInfo.data.value.description) { + this.translate.get(routeInfo.data.value.description).take(1).subscribe((translatedDescription: string) => { + this.addMetaTag('description', translatedDescription); + }); } } diff --git a/src/app/shared/mocks/mock-normalized-item.ts b/src/app/shared/mocks/mock-normalized-item.ts deleted file mode 100644 index d8716b4bf2..0000000000 --- a/src/app/shared/mocks/mock-normalized-item.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { NormalizedItem } from '../../core/cache/models/normalized-item.model'; - -export const MockNormalizedItem: NormalizedItem = Object.assign(new NormalizedItem(), { - handle: '10673/6', - lastModified: new Date('2017-04-24T19:44:08.178+0000'), - isArchived: true, - isDiscoverable: true, - isWithdrawn: false, - bitstreams: [ - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/a7cd7d97-4e40-41db-80a8-fac908b63bb8', - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/9ff3df0d-1709-472f-8c00-d3e8db2153c8', - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/d660a4b8-e7cc-45cd-b026-35f98c5bd3ba' - ], - self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357', - id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', - uuid: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', - type: 'item', - name: 'Test PowerPoint Document', - metadata: [ - { - key: 'dc.creator', - language: 'en_US', - value: 'Doe, Jane L' - }, - { - key: 'dc.date.accessioned', - language: null, - value: '1650-06-26T19:58:25Z' - }, - { - key: 'dc.date.available', - language: null, - value: '1650-06-26T19:58:25Z' - }, - { - key: 'dc.date.issued', - language: null, - value: '1650-06-26' - }, - { - key: 'dc.identifier.issn', - language: 'en_US', - value: '123456789' - }, - { - key: 'dc.identifier.uri', - language: null, - value: 'http://dspace7.4science.it/xmlui/handle/10673/6' - }, - { - key: 'dc.description.abstract', - language: 'en_US', - value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' - }, - { - key: 'dc.language', - language: 'en_US', - value: 'en' - }, - { - key: 'dc.rights', - language: 'en_US', - value: '© Jane Doe' - }, - { - key: 'dc.subject', - language: 'en_US', - value: 'keyword1' - }, - { - key: 'dc.subject', - language: 'en_US', - value: 'keyword2' - }, - { - key: 'dc.subject', - language: 'en_US', - value: 'keyword3' - }, - { - key: 'dc.title', - language: 'en_US', - value: 'Test PowerPoint Document' - }, - { - key: 'dc.type', - language: 'en_US', - value: 'text' - } - ], - owningCollection: [ - 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb' - ] -})