diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts index 6f934f5b4b..46074f7a6b 100644 --- a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts @@ -32,10 +32,22 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC */ protected subs: Subscription[] = []; - protected inAndOutputNames: (keyof this)[] = [ + /** + * The @{@link Input}() that are used to find the matching component using {@link getComponent}. When the value of + * one of these @{@link Input}() change this loader needs to retrieve the best matching component again using the + * {@link getComponent} method. + */ + protected inputNamesDependentForComponent: (keyof this & string)[] = [ 'context', ]; + protected inputNames: (keyof this & string)[] = [ + 'context', + ]; + + protected outputNames: (keyof this & string)[] = [ + ]; + constructor( protected themeService: ThemeService, ) { @@ -45,7 +57,9 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC * Set up the dynamic child component */ ngOnInit(): void { - this.instantiateComponent(); + if (hasNoValue(this.compRef)) { + this.instantiateComponent(); + } } /** @@ -55,14 +69,14 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC 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); + this.instantiateComponent(); } else { - // if an input or output has changed - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { + if (this.inputNamesDependentForComponent.some((name: any) => hasValue(changes[name]) && changes[name].previousValue !== changes[name].currentValue)) { + // Recreate the component when the @Input()s used by getComponent() aren't up-to-date anymore + this.destroyComponentInstance(); + this.instantiateComponent(); + } else { this.connectInputsAndOutputs(); - if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { - (this.compRef.instance as any).ngOnChanges(changes); - } } } } @@ -73,7 +87,10 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC .forEach((subscription: Subscription) => subscription.unsubscribe()); } - public instantiateComponent(changes?: SimpleChanges): void { + /** + * Creates the component and connects the @Input() & @Output() from the ThemedComponent to its child Component. + */ + public instantiateComponent(): void { const component: GenericConstructor = this.getComponent(); const viewContainerRef: ViewContainerRef = this.componentDirective.viewContainerRef; @@ -86,10 +103,16 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC }, ); - if (hasValue(changes)) { - this.ngOnChanges(changes); - } else { - this.connectInputsAndOutputs(); + this.connectInputsAndOutputs(); + } + + /** + * Destroys the themed component and calls it's `ngOnDestroy` + */ + public destroyComponentInstance(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = null; } } @@ -99,12 +122,18 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC public abstract getComponent(): GenericConstructor; /** - * Connect the in and outputs of this component to the dynamic component, + * Connect the inputs 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) => { + if (isNotEmpty(this.inputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { + this.inputNames.filter((name: string) => this[name] !== undefined).filter((name: string) => this[name] !== this.compRef.instance[name]).forEach((name: string) => { + // Using setInput will automatically trigger the ngOnChanges + this.compRef.setInput(name, this[name]); + }); + } + if (isNotEmpty(this.outputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { + this.outputNames.filter((name: string) => this[name] !== undefined).filter((name: string) => this[name] !== this.compRef.instance[name]).forEach((name: string) => { this.compRef.instance[name] = this[name]; }); } diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts index 83542512ed..4b63a31a2b 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts @@ -24,8 +24,8 @@ export class MetadataRepresentationLoaderComponent extends AbstractComponentLoad */ @Input() mdRepresentation: MetadataRepresentation; - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'mdRepresentation', ]; diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts index 75b392fe51..fb39347d63 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnChanges, OnInit, Output, } from '@angular/core'; +import { Component, EventEmitter, Input, Output, } from '@angular/core'; import { getComponentByWorkflowTaskOption } from './claimed-task-actions-decorator'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { MyDSpaceActionsResult } from '../../mydspace-actions'; @@ -16,7 +16,7 @@ import { GenericConstructor } from '../../../../core/shared/generic-constructor' * Component for loading a ClaimedTaskAction component depending on the "option" input * Passes on the ClaimedTask to the component and subscribes to the processCompleted output */ -export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderComponent implements OnInit, OnChanges { +export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderComponent { /** * The item object that belonging to the ClaimedTask object */ @@ -46,12 +46,16 @@ export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderCo /** * The list of input and output names for the dynamic component */ - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'item', 'object', 'option', 'workflowitem', + ]; + + protected outputNames: (keyof this & string)[] = [ + ...this.outputNames, 'processCompleted', ]; diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index f62ee44a1c..a83cdfb6b2 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -1,5 +1,4 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core'; -import { combineLatest, Observable, of as observableOf } from 'rxjs'; +import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; import { take } from 'rxjs/operators'; import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; @@ -74,8 +73,8 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa /** * The list of input and output names for the dynamic component */ - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'object', 'index', 'linkType', @@ -84,6 +83,10 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa 'showThumbnails', 'viewMode', 'value', + ]; + + protected outputNames: (keyof this & string)[] = [ + ...this.outputNames, 'contentChange', ]; @@ -94,17 +97,16 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa super(themeService); } - public instantiateComponent(changes?: SimpleChanges): void { - super.instantiateComponent(changes); + public instantiateComponent(): void { + super.instantiateComponent(); if ((this.compRef.instance as any).reloadedObject) { - combineLatest([ - observableOf(changes), - (this.compRef.instance as any).reloadedObject.pipe(take(1)) as Observable, - ]).subscribe(([simpleChanges, reloadedObject]: [SimpleChanges, DSpaceObject]) => { + (this.compRef.instance as any).reloadedObject.pipe( + take(1), + ).subscribe((reloadedObject: DSpaceObject) => { if (reloadedObject) { - this.compRef.destroy(); + this.destroyComponentInstance(); this.object = reloadedObject; - this.instantiateComponent(simpleChanges); + this.instantiateComponent(); this.cdr.detectChanges(); this.contentChange.emit(reloadedObject); } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts index 10cae7ec7d..1db49b97e8 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts @@ -24,8 +24,8 @@ export class AdvancedWorkflowActionsLoaderComponent extends AbstractComponentLoa */ @Input() type: string; - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'type', ];