Make MetadataRepresentationLoaderComponent extend AbstractComponentLoaderComponent

This commit is contained in:
Alexandre Vryghem
2023-06-10 16:14:54 +02:00
parent 774784a9b9
commit 2bae174350
5 changed files with 20 additions and 111 deletions

View File

@@ -1 +0,0 @@
<ng-container dsMetadataRepresentation></ng-container>

View File

@@ -6,10 +6,11 @@ import {
MetadataRepresentationType MetadataRepresentationType
} from '../../core/shared/metadata-representation/metadata-representation.model'; } from '../../core/shared/metadata-representation/metadata-representation.model';
import { MetadataRepresentationLoaderComponent } from './metadata-representation-loader.component'; import { MetadataRepresentationLoaderComponent } from './metadata-representation-loader.component';
import { MetadataRepresentationDirective } from './metadata-representation.directive'; import { DynamicComponentLoaderDirective } from '../abstract-component-loader/dynamic-component-loader.directive';
import { METADATA_REPRESENTATION_COMPONENT_FACTORY } from './metadata-representation.decorator'; import { METADATA_REPRESENTATION_COMPONENT_FACTORY } from './metadata-representation.decorator';
import { ThemeService } from '../theme-support/theme.service'; import { ThemeService } from '../theme-support/theme.service';
import { PlainTextMetadataListElementComponent } from '../object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component'; import { PlainTextMetadataListElementComponent } from '../object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component';
import { getMockThemeService } from '../mocks/theme-service.mock';
const testType = 'TestType'; const testType = 'TestType';
const testContext = Context.Search; const testContext = Context.Search;
@@ -36,12 +37,14 @@ describe('MetadataRepresentationLoaderComponent', () => {
const themeName = 'test-theme'; const themeName = 'test-theme';
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
themeService = jasmine.createSpyObj('themeService', { themeService = getMockThemeService(themeName);
getThemeName: themeName,
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [], imports: [],
declarations: [MetadataRepresentationLoaderComponent, PlainTextMetadataListElementComponent, MetadataRepresentationDirective], declarations: [
MetadataRepresentationLoaderComponent,
PlainTextMetadataListElementComponent,
DynamicComponentLoaderDirective,
],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
providers: [ providers: [
{ {
@@ -64,6 +67,7 @@ describe('MetadataRepresentationLoaderComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(MetadataRepresentationLoaderComponent); fixture = TestBed.createComponent(MetadataRepresentationLoaderComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
spyOn(comp, 'getComponent').and.callThrough();
comp.mdRepresentation = new TestType(); comp.mdRepresentation = new TestType();
comp.context = testContext; comp.context = testContext;
@@ -71,8 +75,8 @@ describe('MetadataRepresentationLoaderComponent', () => {
})); }));
describe('When the component is rendered', () => { describe('When the component is rendered', () => {
it('should call the getMetadataRepresentationComponent function with the right entity type, representation type and context', () => { it('should call the getComponent function', () => {
expect((comp as any).getMetadataRepresentationComponent).toHaveBeenCalledWith(testType, testRepresentationType, testContext, themeName); expect(comp.getComponent).toHaveBeenCalled();
}); });
}); });
}); });

View File

@@ -1,4 +1,4 @@
import { Component, ComponentFactoryResolver, Inject, Input, OnInit, ViewChild, OnChanges, SimpleChanges, ComponentRef, ViewContainerRef, ComponentFactory } from '@angular/core'; import { Component, Inject, Input } from '@angular/core';
import { import {
MetadataRepresentation, MetadataRepresentation,
MetadataRepresentationType MetadataRepresentationType
@@ -7,118 +7,37 @@ import { METADATA_REPRESENTATION_COMPONENT_FACTORY } from './metadata-representa
import { Context } from '../../core/shared/context.model'; import { Context } from '../../core/shared/context.model';
import { GenericConstructor } from '../../core/shared/generic-constructor'; import { GenericConstructor } from '../../core/shared/generic-constructor';
import { MetadataRepresentationListElementComponent } from '../object-list/metadata-representation-list-element/metadata-representation-list-element.component'; import { MetadataRepresentationListElementComponent } from '../object-list/metadata-representation-list-element/metadata-representation-list-element.component';
import { MetadataRepresentationDirective } from './metadata-representation.directive';
import { hasValue, isNotEmpty, hasNoValue } from '../empty.util';
import { ThemeService } from '../theme-support/theme.service'; import { ThemeService } from '../theme-support/theme.service';
import { AbstractComponentLoaderComponent } from '../abstract-component-loader/abstract-component-loader.component';
@Component({ @Component({
selector: 'ds-metadata-representation-loader', selector: 'ds-metadata-representation-loader',
templateUrl: './metadata-representation-loader.component.html' templateUrl: '../abstract-component-loader/abstract-component-loader.component.html',
}) })
/** /**
* Component for determining what component to use depending on the item's entity type (dspace.entity.type), its metadata representation and, optionally, its context * Component for determining what component to use depending on the item's entity type (dspace.entity.type), its metadata representation and, optionally, its context
*/ */
export class MetadataRepresentationLoaderComponent implements OnInit, OnChanges { export class MetadataRepresentationLoaderComponent extends AbstractComponentLoaderComponent<MetadataRepresentationListElementComponent> {
/** /**
* The item or metadata to determine the component for * The item or metadata to determine the component for
*/ */
private _mdRepresentation: MetadataRepresentation; @Input() mdRepresentation: MetadataRepresentation;
get mdRepresentation(): MetadataRepresentation {
return this._mdRepresentation;
}
@Input() set mdRepresentation(nextValue: MetadataRepresentation) {
this._mdRepresentation = nextValue;
if (hasValue(this.compRef?.instance)) {
this.compRef.instance.mdRepresentation = nextValue;
}
}
/**
* The optional context
*/
@Input() context: Context;
/**
* Directive to determine where the dynamic child component is located
*/
@ViewChild(MetadataRepresentationDirective, {static: true}) mdRepDirective: MetadataRepresentationDirective;
/**
* The reference to the dynamic component
*/
protected compRef: ComponentRef<MetadataRepresentationListElementComponent>;
protected inAndOutputNames: (keyof this)[] = [ protected inAndOutputNames: (keyof this)[] = [
'context', ...this.inAndOutputNames,
'mdRepresentation', 'mdRepresentation',
]; ];
constructor( constructor(
private componentFactoryResolver: ComponentFactoryResolver, protected themeService: ThemeService,
private themeService: ThemeService,
@Inject(METADATA_REPRESENTATION_COMPONENT_FACTORY) private getMetadataRepresentationComponent: (entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context, theme: string) => GenericConstructor<any>, @Inject(METADATA_REPRESENTATION_COMPONENT_FACTORY) private getMetadataRepresentationComponent: (entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context, theme: string) => GenericConstructor<any>,
) { ) {
super(themeService);
} }
/** public getComponent(): GenericConstructor<MetadataRepresentationListElementComponent> {
* Set up the dynamic child component
*/
ngOnInit(): void {
this.instantiateComponent();
}
/**
* Whenever the inputs change, update the inputs of the dynamic component
*/
ngOnChanges(changes: SimpleChanges): void {
if (hasNoValue(this.compRef)) {
// sometimes the component has not been initialized yet, so it first needs to be initialized
// before being called again
this.instantiateComponent(changes);
} else {
// if an input or output has changed
if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) {
this.connectInputsAndOutputs();
if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) {
(this.compRef.instance as any).ngOnChanges(changes);
}
}
}
}
private instantiateComponent(changes?: SimpleChanges): void {
const componentFactory: ComponentFactory<MetadataRepresentationListElementComponent> = this.componentFactoryResolver.resolveComponentFactory(this.getComponent());
const viewContainerRef: ViewContainerRef = this.mdRepDirective.viewContainerRef;
viewContainerRef.clear();
this.compRef = viewContainerRef.createComponent(componentFactory);
if (hasValue(changes)) {
this.ngOnChanges(changes);
} else {
this.connectInputsAndOutputs();
}
}
/**
* Fetch the component depending on the item's entity type, metadata representation type and context
* @returns {string}
*/
private getComponent(): GenericConstructor<MetadataRepresentationListElementComponent> {
return this.getMetadataRepresentationComponent(this.mdRepresentation.itemType, this.mdRepresentation.representationType, this.context, this.themeService.getThemeName()); return this.getMetadataRepresentationComponent(this.mdRepresentation.itemType, this.mdRepresentation.representationType, this.context, this.themeService.getThemeName());
} }
/**
* Connect the in and outputs of this component to the dynamic component,
* to ensure they're in sync
*/
protected connectInputsAndOutputs(): void {
if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) {
this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => {
this.compRef.instance[name] = this[name];
});
}
}
} }

View File

@@ -1,11 +0,0 @@
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[dsMetadataRepresentation]',
})
/**
* Directive used as a hook to know where to inject the dynamic metadata representation component
*/
export class MetadataRepresentationDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}

View File

@@ -170,7 +170,6 @@ import { AccessStatusBadgeComponent } from './object-collection/shared/badges/ac
import { import {
MetadataRepresentationLoaderComponent MetadataRepresentationLoaderComponent
} from './metadata-representation/metadata-representation-loader.component'; } from './metadata-representation/metadata-representation-loader.component';
import { MetadataRepresentationDirective } from './metadata-representation/metadata-representation.directive';
import { import {
ListableObjectComponentLoaderComponent ListableObjectComponentLoaderComponent
} from './object-collection/shared/listable-object/listable-object-component-loader.component'; } from './object-collection/shared/listable-object/listable-object-component-loader.component';
@@ -481,7 +480,6 @@ const DIRECTIVES = [
InListValidator, InListValidator,
AutoFocusDirective, AutoFocusDirective,
RoleDirective, RoleDirective,
MetadataRepresentationDirective,
FileValueAccessorDirective, FileValueAccessorDirective,
FileValidator, FileValidator,
NgForTrackByIdDirective, NgForTrackByIdDirective,