htmlangulardspace

Add third-party NPM module to dspace 7 angular app


I would like to make use of the ngx-youtube-player module in the DSpace 7 Angular App, specifically in the untyped-item.component.html. I have place

untyped-item.component.html:

<div class="row" *ngIf="iiifEnabled">
  <div class="col-12">
    <ds-mirador-viewer id="iiif-viewer"
                   [object]="object"
                   [searchable]="iiifSearchEnabled"
                   [query]="iiifQuery$ | async">
    </ds-mirador-viewer>
  </div>
</div>
<div class="d-flex flex-row">
  <ds-item-page-title-field [item]="object" class="mr-auto">
  </ds-item-page-title-field>
  <div class="pl-2 space-children-mr">
    <ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
                                [tooltipMsgCreate]="'item.page.version.create'"
                                [tooltipMsgHasDraft]="'item.page.version.hasDraft'"></ds-dso-page-version-button>
    <ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
  </div>
</div>
<div class="row">
  <div class="col-xs-12 col-md-4">
    <ng-container *ngIf="!mediaViewer.image">
      <ds-metadata-field-wrapper [hideIfNoTextContent]="false">
        <ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
      </ds-metadata-field-wrapper>
    </ng-container>
    <ng-container *ngIf="mediaViewer.image">
      <ds-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-media-viewer>
    </ng-container>
    
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
    <ds-item-page-date-field [item]="object"></ds-item-page-date-field>
    <ds-metadata-representation-list class="ds-item-page-mixed-author-field"
      [parentItem]="object"
      [itemType]="'Person'"
      [metadataFields]="['dc.contributor.author', 'dc.creator']"
      [label]="'relationships.isAuthorOf' | translate">
    </ds-metadata-representation-list>
    <ds-generic-item-page-field [item]="object"
      [fields]="['journal.title']"
      [label]="'item.page.journal-title'">
    </ds-generic-item-page-field>
    <ds-generic-item-page-field [item]="object"
      [fields]="['journal.identifier.issn']"
      [label]="'item.page.journal-issn'">
    </ds-generic-item-page-field>
    <ds-generic-item-page-field [item]="object"
      [fields]="['journalvolume.identifier.name']"
      [label]="'item.page.volume-title'">
    </ds-generic-item-page-field>
    <ds-generic-item-page-field [item]="object"
      [fields]="['dc.publisher']"
      [label]="'item.page.publisher'">
    </ds-generic-item-page-field>
  </div>
  <div class="col-xs-12 col-md-6">
    <ds-item-page-abstract-field [item]="object"></ds-item-page-abstract-field>
    <ds-generic-item-page-field [item]="object"
      [fields]="['dc.description']"
      [label]="'item.page.description'">
    </ds-generic-item-page-field>

    <ds-generic-item-page-field [item]="object"
      [fields]="['dc.subject']"
      [separator]="','"
      [label]="'item.page.subject'">
    </ds-generic-item-page-field>
    <ds-generic-item-page-field [item]="object"
      [fields]="['dc.identifier.citation']"
      [label]="'item.page.citation'">
    </ds-generic-item-page-field>
    <ds-item-page-uri-field [item]="object"
      [fields]="['dc.identifier.uri']"
      [label]="'item.page.uri'">
    </ds-item-page-uri-field>
<youtube-player 
      [videoId]="videoID" 
      (ready)="onPlayerReady($event)"
    ></youtube-player>
    <ds-item-page-collections [item]="object"></ds-item-page-collections>
    <div>
      <a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button">
        <i class="fas fa-info-circle"></i> {{"item.page.link.full" | translate}}
      </a>
    </div>
  </div>
</div>

Below is the untyped-item.component.ts

  import { ChangeDetectionStrategy, Component } from '@angular/core';
    import { Item } from '../../../../core/shared/item.model';
    import { ViewMode } from '../../../../core/shared/view-mode.model';
    import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
    import { VersionedItemComponent } from '../versioned-item/versioned-item.component';
    
    /**
     * Component that represents a publication Item page
     */
    
    @listableObjectComponent(Item, ViewMode.StandalonePage)
    @Component({
      selector: 'ds-untyped-item',
      styleUrls: ['./untyped-item.component.scss'],
      templateUrl: './untyped-item.component.html',
      changeDetection: ChangeDetectionStrategy.OnPush,
    })
    export class UntypedItemComponent extends VersionedItemComponent implements AfterViewInit 
{
    videoID: string = '';
    ngAfterViewInit() 
    {
      let uri = '';
      for (var index in this.object.metadata['dc.identifier.uri']) 
      {
 
        if (this.object.metadata['dc.identifier.uri'][index].value.includes("www.youtube"))
        {
          uri = this.object.metadata['dc.identifier.uri'][index].value; 
        }
      }
      let url = new URL(uri);
      const searchParams = url.searchParams;
      this.videoID = searchParams.get('v'); 
    }
  }    

I've imported the ngx-youtube-player module in item-page.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { SharedModule } from '../shared/shared.module';

import { ItemPageComponent } from './simple/item-page.component';
import { ItemPageRoutingModule } from './item-page-routing.module';
import { MetadataUriValuesComponent } from './field-components/metadata-uri-values/metadata-uri-values.component';
import {
  ItemPageAuthorFieldComponent
} from './simple/field-components/specific-field/author/item-page-author-field.component';
import {
  ItemPageDateFieldComponent
} from './simple/field-components/specific-field/date/item-page-date-field.component';
import {
  ItemPageAbstractFieldComponent
} from './simple/field-components/specific-field/abstract/item-page-abstract-field.component';
import { ItemPageUriFieldComponent } from './simple/field-components/specific-field/uri/item-page-uri-field.component';
import { ItemPageFieldComponent } from './simple/field-components/specific-field/item-page-field.component';
import { CollectionsComponent } from './field-components/collections/collections.component';
import { FullItemPageComponent } from './full/full-item-page.component';
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
import { PublicationComponent } from './simple/item-types/publication/publication.component';
import { ItemComponent } from './simple/item-types/shared/item.component';
import { EditItemPageModule } from './edit-item-page/edit-item-page.module';
import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component';
import { StatisticsModule } from '../statistics/statistics.module';
import {
  AbstractIncrementalListComponent
} from './simple/abstract-incremental-list/abstract-incremental-list.component';
import { UntypedItemComponent } from './simple/item-types/untyped-item/untyped-item.component';
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
import { ThemedItemPageComponent } from './simple/themed-item-page.component';
import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component';
import { MediaViewerComponent } from './media-viewer/media-viewer.component';
import { MediaViewerVideoComponent } from './media-viewer/media-viewer-video/media-viewer-video.component';
import { MediaViewerImageComponent } from './media-viewer/media-viewer-image/media-viewer-image.component';
import { NgxGalleryModule } from '@kolkov/ngx-gallery';
import { MiradorViewerComponent } from './mirador-viewer/mirador-viewer.component';
import { VersionPageComponent } from './version-page/version-page/version-page.component';
import { VersionedItemComponent } from './simple/item-types/versioned-item/versioned-item.component';
import { ThemedFileSectionComponent } from './simple/field-components/file-section/themed-file-section.component';
import { OrcidAuthComponent } from './orcid-page/orcid-auth/orcid-auth.component';
import { OrcidPageComponent } from './orcid-page/orcid-page.component';
import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap';
import { OrcidSyncSettingsComponent } from './orcid-page/orcid-sync-settings/orcid-sync-settings.component';
import { OrcidQueueComponent } from './orcid-page/orcid-queue/orcid-queue.component';
import { NgxYoutubePlayerModule } from 'ngx-youtube-player';

const ENTRY_COMPONENTS = [
  // put only entry components that use custom decorator
  PublicationComponent,
  UntypedItemComponent
];

const DECLARATIONS = [
  ThemedFileSectionComponent,
  ItemPageComponent,
  ThemedItemPageComponent,
  FullItemPageComponent,
  ThemedFullItemPageComponent,
  MetadataUriValuesComponent,
  ItemPageAuthorFieldComponent,
  ItemPageDateFieldComponent,
  ItemPageAbstractFieldComponent,
  ItemPageUriFieldComponent,
  ItemPageFieldComponent,
  CollectionsComponent,
  FullFileSectionComponent,
  PublicationComponent,
  UntypedItemComponent,
  ItemComponent,
  UploadBitstreamComponent,
  AbstractIncrementalListComponent,
  MediaViewerComponent,
  MediaViewerVideoComponent,
  MediaViewerImageComponent,
  MiradorViewerComponent,
  VersionPageComponent,
  OrcidPageComponent,
  OrcidAuthComponent,
  OrcidSyncSettingsComponent,
  OrcidQueueComponent,
];

@NgModule({
  imports: [
    CommonModule,
    SharedModule.withEntryComponents(),
    ItemPageRoutingModule,
    EditItemPageModule,
    StatisticsModule.forRoot(),
    JournalEntitiesModule.withEntryComponents(),
    ResearchEntitiesModule.withEntryComponents(),
    NgxGalleryModule,
    NgbAccordionModule,
    NgxYoutubePlayerModule
  ],
  declarations: [
    ...DECLARATIONS,
    VersionedItemComponent
  ],
  exports: [
    ...DECLARATIONS
  ]
})
export class ItemPageModule {
  /**
   * NOTE: this method allows to resolve issue with components that using a custom decorator
   * which are not loaded during SSR otherwise
   */
  static withEntryComponents() {
    return {
      ngModule: ItemPageModule,
      providers: ENTRY_COMPONENTS.map((component) => ({provide: component}))
    };
  }

}

I however, still get the following error:

Error: src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html:129:7 - error NG8002: Can't bind to 'videoId' since it isn't a known property of 'youtube-player'.

  1. If 'youtube-player' is an Angular component and it has 'videoId' input, then verify that it is part of this module.
  2. If 'youtube-player' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
  3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.

What am I doing wrong. I even tried to import it in app.module.ts but still the same error. Where should I be importing the module for it to be picked up in untyped-item.component.html. From what I can see in DSpace 7, is that untyped-item.component is the child of item-page.component.

Thanking you in advance.


Solution

  • So the missing piece of the puzzle was to import the module in eager-theme.module.ts and lazy-theme.module.ts. After importing there, the package should pick up. This applies to other NPM packages as well.