mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Make ListableObjectComponentLoaderComponent extend AbstractComponentLoaderComponent
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<ng-template dsListableObject>
|
||||
<ng-template dsDynamicComponentLoader>
|
||||
</ng-template>
|
||||
<div #badges>
|
||||
<ng-content></ng-content>
|
||||
|
@@ -11,7 +11,7 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g
|
||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
||||
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
||||
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
||||
import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||
|
||||
@@ -25,7 +25,7 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service
|
||||
* The component for displaying a list element for an item search result on the admin search page
|
||||
*/
|
||||
export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> implements OnInit {
|
||||
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective;
|
||||
@ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective;
|
||||
@ViewChild('badges', { static: true }) badges: ElementRef;
|
||||
@ViewChild('buttons', { static: true }) buttons: ElementRef;
|
||||
|
||||
@@ -46,7 +46,7 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE
|
||||
super.ngOnInit();
|
||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent());
|
||||
|
||||
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
|
||||
const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef;
|
||||
viewContainerRef.clear();
|
||||
|
||||
const componentRef = viewContainerRef.createComponent(
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<ng-template dsListableObject>
|
||||
<ng-template dsDynamicComponentLoader>
|
||||
</ng-template>
|
||||
<div #badges class="position-absolute ml-1">
|
||||
<div class="workflow-badge">
|
||||
|
@@ -18,8 +18,8 @@ import {
|
||||
ItemGridElementComponent
|
||||
} from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
|
||||
import {
|
||||
ListableObjectDirective
|
||||
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
||||
DynamicComponentLoaderDirective
|
||||
} from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||
import {
|
||||
WorkflowItemSearchResult
|
||||
} from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||
@@ -38,7 +38,7 @@ describe('WorkflowItemSearchResultAdminWorkflowGridElementComponent', () => {
|
||||
let itemRD$;
|
||||
let linkService;
|
||||
let object;
|
||||
let themeService;
|
||||
let themeService: ThemeService;
|
||||
|
||||
function init() {
|
||||
itemRD$ = createSuccessfulRemoteDataObject$(new Item());
|
||||
@@ -55,7 +55,11 @@ describe('WorkflowItemSearchResultAdminWorkflowGridElementComponent', () => {
|
||||
init();
|
||||
TestBed.configureTestingModule(
|
||||
{
|
||||
declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective],
|
||||
declarations: [
|
||||
WorkflowItemSearchResultAdminWorkflowGridElementComponent,
|
||||
ItemGridElementComponent,
|
||||
DynamicComponentLoaderDirective,
|
||||
],
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
TranslateModule.forRoot(),
|
||||
|
@@ -10,7 +10,7 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g
|
||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
||||
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
||||
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
||||
import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||
import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||
@@ -38,7 +38,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S
|
||||
/**
|
||||
* Directive used to render the dynamic component in
|
||||
*/
|
||||
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective;
|
||||
@ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective;
|
||||
|
||||
/**
|
||||
* The html child that contains the badges html
|
||||
@@ -77,7 +77,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S
|
||||
this.item$.pipe(take(1)).subscribe((item: Item) => {
|
||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item));
|
||||
|
||||
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
|
||||
const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef;
|
||||
viewContainerRef.clear();
|
||||
|
||||
const componentRef = viewContainerRef.createComponent(
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<ng-template dsListableObject>
|
||||
<ng-template dsDynamicComponentLoader>
|
||||
</ng-template>
|
||||
<div #badges class="position-absolute ml-1">
|
||||
<div class="workflow-badge">
|
||||
|
@@ -20,8 +20,8 @@ import {
|
||||
ItemGridElementComponent
|
||||
} from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
|
||||
import {
|
||||
ListableObjectDirective
|
||||
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
||||
DynamicComponentLoaderDirective
|
||||
} from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||
import {
|
||||
WorkflowItemSearchResult
|
||||
} from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||
@@ -45,7 +45,7 @@ describe('WorkspaceItemSearchResultAdminWorkflowGridElementComponent', () => {
|
||||
let itemRD$;
|
||||
let linkService;
|
||||
let object;
|
||||
let themeService;
|
||||
let themeService: ThemeService;
|
||||
let supervisionOrderDataService;
|
||||
|
||||
function init() {
|
||||
@@ -67,7 +67,11 @@ describe('WorkspaceItemSearchResultAdminWorkflowGridElementComponent', () => {
|
||||
init();
|
||||
TestBed.configureTestingModule(
|
||||
{
|
||||
declarations: [WorkspaceItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective],
|
||||
declarations: [
|
||||
WorkspaceItemSearchResultAdminWorkflowGridElementComponent,
|
||||
ItemGridElementComponent,
|
||||
DynamicComponentLoaderDirective,
|
||||
],
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
TranslateModule.forRoot(),
|
||||
|
@@ -16,9 +16,7 @@ import {
|
||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
||||
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
||||
import {
|
||||
ListableObjectDirective
|
||||
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
||||
import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||
import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model';
|
||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
|
||||
@@ -67,7 +65,7 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends
|
||||
/**
|
||||
* Directive used to render the dynamic component in
|
||||
*/
|
||||
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective;
|
||||
@ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective;
|
||||
|
||||
/**
|
||||
* The html child that contains the badges html
|
||||
@@ -102,7 +100,7 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends
|
||||
this.item$.pipe(take(1)).subscribe((item: Item) => {
|
||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item));
|
||||
|
||||
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
|
||||
const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef;
|
||||
viewContainerRef.clear();
|
||||
|
||||
const componentRef = viewContainerRef.createComponent(
|
||||
|
@@ -1 +0,0 @@
|
||||
<ng-template dsListableObject></ng-template>
|
@@ -8,7 +8,7 @@ import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
import {
|
||||
ItemListElementComponent
|
||||
} from '../../../object-list/item-list-element/item-types/item/item-list-element.component';
|
||||
import { ListableObjectDirective } from './listable-object.directive';
|
||||
import { DynamicComponentLoaderDirective } from '../../../abstract-component-loader/dynamic-component-loader.directive';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
@@ -36,7 +36,7 @@ describe('ListableObjectComponentLoaderComponent', () => {
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot()],
|
||||
declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, ListableObjectDirective],
|
||||
declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, DynamicComponentLoaderDirective],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
providers: [
|
||||
provideMockStore({}),
|
||||
@@ -65,7 +65,7 @@ describe('ListableObjectComponentLoaderComponent', () => {
|
||||
|
||||
describe('When the component is rendered', () => {
|
||||
it('should call the getListableObjectComponent function with the right types, view mode and context', () => {
|
||||
expect(comp.getComponent).toHaveBeenCalledWith([testType], testViewMode, testContext);
|
||||
expect(comp.getComponent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should connectInputsAndOutputs of loaded component', () => {
|
||||
@@ -78,29 +78,29 @@ describe('ListableObjectComponentLoaderComponent', () => {
|
||||
let reloadedObject: any;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn((comp as any), 'instantiateComponent').and.returnValue(null);
|
||||
spyOn((comp as any).contentChange, 'emit').and.returnValue(null);
|
||||
spyOn(comp, 'instantiateComponent').and.returnValue(null);
|
||||
spyOn(comp.contentChange, 'emit').and.returnValue(null);
|
||||
|
||||
listableComponent = fixture.debugElement.query(By.css('ds-item-list-element')).componentInstance;
|
||||
reloadedObject = 'object';
|
||||
});
|
||||
|
||||
it('should re-instantiate the listable component', fakeAsync(() => {
|
||||
expect((comp as any).instantiateComponent).not.toHaveBeenCalled();
|
||||
expect(comp.instantiateComponent).not.toHaveBeenCalled();
|
||||
|
||||
(listableComponent as any).reloadedObject.emit(reloadedObject);
|
||||
listableComponent.reloadedObject.emit(reloadedObject);
|
||||
tick(200);
|
||||
|
||||
expect((comp as any).instantiateComponent).toHaveBeenCalledWith(reloadedObject, undefined);
|
||||
expect(comp.instantiateComponent).toHaveBeenCalledWith(undefined);
|
||||
}));
|
||||
|
||||
it('should re-emit it as a contentChange', fakeAsync(() => {
|
||||
expect((comp as any).contentChange.emit).not.toHaveBeenCalled();
|
||||
expect(comp.contentChange.emit).not.toHaveBeenCalled();
|
||||
|
||||
(listableComponent as any).reloadedObject.emit(reloadedObject);
|
||||
listableComponent.reloadedObject.emit(reloadedObject);
|
||||
tick(200);
|
||||
|
||||
expect((comp as any).contentChange.emit).toHaveBeenCalledWith(reloadedObject);
|
||||
expect(comp.contentChange.emit).toHaveBeenCalledWith(reloadedObject);
|
||||
}));
|
||||
|
||||
});
|
||||
|
@@ -1,40 +1,26 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ComponentRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
||||
import { Subscription, combineLatest, of as observableOf, Observable } from 'rxjs';
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core';
|
||||
import { combineLatest, Observable, of as observableOf } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { ListableObject } from '../listable-object.model';
|
||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
import { Context } from '../../../../core/shared/context.model';
|
||||
import { Context } from 'src/app/core/shared/context.model';
|
||||
import { getListableObjectComponent } from './listable-object.decorator';
|
||||
import { GenericConstructor } from '../../../../core/shared/generic-constructor';
|
||||
import { ListableObjectDirective } from './listable-object.directive';
|
||||
import { CollectionElementLinkType } from '../../collection-element-link.type';
|
||||
import { hasValue, isNotEmpty, hasNoValue } from '../../../empty.util';
|
||||
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||
import { ThemeService } from '../../../theme-support/theme.service';
|
||||
import { AbstractComponentLoaderComponent } from '../../../abstract-component-loader/abstract-component-loader.component';
|
||||
import { ThemeService } from 'src/app/shared/theme-support/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-listable-object-component-loader',
|
||||
styleUrls: ['./listable-object-component-loader.component.scss'],
|
||||
templateUrl: './listable-object-component-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)
|
||||
*/
|
||||
export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges, OnDestroy {
|
||||
export class ListableObjectComponentLoaderComponent extends AbstractComponentLoaderComponent<Component> {
|
||||
|
||||
/**
|
||||
* The item or metadata to determine the component for
|
||||
*/
|
||||
@@ -80,99 +66,36 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges
|
||||
*/
|
||||
@Input() value: string;
|
||||
|
||||
/**
|
||||
* Directive hook used to place the dynamic child component
|
||||
*/
|
||||
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective;
|
||||
|
||||
/**
|
||||
* Emit when the listable object has been reloaded.
|
||||
*/
|
||||
@Output() contentChange = new EventEmitter<ListableObject>();
|
||||
|
||||
/**
|
||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||
* @type {Array}
|
||||
*/
|
||||
protected subs: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* The reference to the dynamic component
|
||||
*/
|
||||
protected compRef: ComponentRef<Component>;
|
||||
|
||||
/**
|
||||
* The list of input and output names for the dynamic component
|
||||
*/
|
||||
protected inAndOutputNames: string[] = [
|
||||
protected inAndOutputNames: (keyof this)[] = [
|
||||
...this.inAndOutputNames,
|
||||
'object',
|
||||
'index',
|
||||
'linkType',
|
||||
'listID',
|
||||
'showLabel',
|
||||
'showThumbnails',
|
||||
'context',
|
||||
'viewMode',
|
||||
'value',
|
||||
'hideBadges',
|
||||
'contentChange',
|
||||
];
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef, private themeService: ThemeService) {
|
||||
constructor(
|
||||
protected themeService: ThemeService,
|
||||
protected cdr: ChangeDetectorRef,
|
||||
) {
|
||||
super(themeService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the dynamic child component
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.instantiateComponent(this.object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(this.object, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subs
|
||||
.filter((subscription) => hasValue(subscription))
|
||||
.forEach((subscription) => subscription.unsubscribe());
|
||||
}
|
||||
|
||||
private instantiateComponent(object: ListableObject, changes?: SimpleChanges): void {
|
||||
|
||||
const component = this.getComponent(object.getRenderTypes(), this.viewMode, this.context);
|
||||
|
||||
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
|
||||
viewContainerRef.clear();
|
||||
|
||||
this.compRef = viewContainerRef.createComponent(
|
||||
component, {
|
||||
index: 0,
|
||||
injector: undefined
|
||||
}
|
||||
);
|
||||
|
||||
if (hasValue(changes)) {
|
||||
this.ngOnChanges(changes);
|
||||
} else {
|
||||
this.connectInputsAndOutputs();
|
||||
}
|
||||
|
||||
public instantiateComponent(changes?: SimpleChanges): void {
|
||||
super.instantiateComponent(changes);
|
||||
if ((this.compRef.instance as any).reloadedObject) {
|
||||
combineLatest([
|
||||
observableOf(changes),
|
||||
@@ -181,7 +104,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges
|
||||
if (reloadedObject) {
|
||||
this.compRef.destroy();
|
||||
this.object = reloadedObject;
|
||||
this.instantiateComponent(reloadedObject, simpleChanges);
|
||||
this.instantiateComponent(simpleChanges);
|
||||
this.cdr.detectChanges();
|
||||
this.contentChange.emit(reloadedObject);
|
||||
}
|
||||
@@ -189,26 +112,8 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the component depending on the item's entity type, view mode and context
|
||||
* @returns {GenericConstructor<Component>}
|
||||
*/
|
||||
getComponent(renderTypes: (string | GenericConstructor<ListableObject>)[],
|
||||
viewMode: ViewMode,
|
||||
context: Context): GenericConstructor<Component> {
|
||||
return getListableObjectComponent(renderTypes, viewMode, 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];
|
||||
});
|
||||
}
|
||||
public getComponent(): GenericConstructor<Component> {
|
||||
return getListableObjectComponent(this.object.getRenderTypes(), this.viewMode, this.context, this.themeService.getThemeName());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,11 +0,0 @@
|
||||
import { Directive, ViewContainerRef } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[dsListableObject]',
|
||||
})
|
||||
/**
|
||||
* Directive used as a hook to know where to inject the dynamic listable object component
|
||||
*/
|
||||
export class ListableObjectDirective {
|
||||
constructor(public viewContainerRef: ViewContainerRef) { }
|
||||
}
|
@@ -177,7 +177,6 @@ import {
|
||||
import {
|
||||
ItemSearchResultListElementComponent
|
||||
} from './object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component';
|
||||
import { ListableObjectDirective } from './object-collection/shared/listable-object/listable-object.directive';
|
||||
import {
|
||||
ItemMetadataRepresentationListElementComponent
|
||||
} from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component';
|
||||
@@ -484,7 +483,6 @@ const DIRECTIVES = [
|
||||
AutoFocusDirective,
|
||||
RoleDirective,
|
||||
MetadataRepresentationDirective,
|
||||
ListableObjectDirective,
|
||||
ClaimedTaskActionsDirective,
|
||||
FileValueAccessorDirective,
|
||||
FileValidator,
|
||||
|
Reference in New Issue
Block a user