From 9d181c1b74a5177f4399d5a8935643ea0d595b1e Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 28 Sep 2026 22:06:46 +0100 Subject: [PATCH 1/5] [OP-20342] Exempt test code from any-typing rules Turns off no-explicit-any and the no-unsafe-* rules for specs and the test helpers that are kept out of the production bundle. Test doubles and expectation helpers routinely work with loosely typed values, and the typing effort under OP-20341 targets application code only. The two no-unsafe exemptions that lived in the vitest spec block move into the new block, which covers the helper files as well. Removes the inline disables in specs and test setup that the exemption makes redundant. https://community.openproject.org/wp/OP-20342 --- frontend/eslint.config.mjs | 15 +++++++++++---- .../current-project.service.spec.ts | 1 - .../components/wp-query/url-params-helper.spec.ts | 1 - .../keep-tab/keep-tab.service.spec.ts | 2 -- .../view-services/wp-view-order.service.spec.ts | 3 --- .../primer/dynamic-icon.directive.spec.ts | 2 -- .../primer/icon-button.component.spec.ts | 2 -- .../controllers/checkable.controller.spec.ts | 2 +- .../dynamic/reporting/page.controller.spec.ts | 2 -- .../expandable-text.controller.spec.ts | 2 -- frontend/src/test-setup.ts | 5 ----- frontend/src/turbo/turbo-navigation-patch.spec.ts | 1 - 12 files changed, 12 insertions(+), 26 deletions(-) diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index ee266a20e0dc..8e9338969001 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -218,14 +218,21 @@ export default defineConfig([ rules: { ...vitest.configs.recommended.rules, - // vitest expect(...) is always any - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - // Allow more than one class definitions per file (test components) 'max-classes-per-file': 'off', }, }, + { + files: ['**/*.spec.ts', '**/testing/**/*.ts', 'src/stimulus/test-helpers.ts', 'src/test-*.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + }, + }, { // esbuild follows imports past the tsconfig exclude, so the import site // is the boundary keeping test helpers out of the production bundle. diff --git a/frontend/src/app/core/current-project/current-project.service.spec.ts b/frontend/src/app/core/current-project/current-project.service.spec.ts index 6db59e0f76ba..5661fe258745 100644 --- a/frontend/src/app/core/current-project/current-project.service.spec.ts +++ b/frontend/src/app/core/current-project/current-project.service.spec.ts @@ -46,7 +46,6 @@ describe('currentProject service', () => { providers: [ CurrentProjectService, PathHelperService, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment { provide: ApiV3Service, useValue: apiV3Stub }, ], }); diff --git a/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.spec.ts b/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.spec.ts index 133c917211ff..0742e265499a 100644 --- a/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.spec.ts +++ b/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.spec.ts @@ -40,7 +40,6 @@ describe('UrlParamsHelper', () => { TestBed.configureTestingModule({ providers: [ UrlParamsHelperService, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment { provide: PaginationService, useValue: paginationStub }, ], }); diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.spec.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.spec.ts index a74513355449..bc8813b97e2c 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.spec.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.spec.ts @@ -44,10 +44,8 @@ describe('keepTab service', () => { TestBed.configureTestingModule({ providers: [ KeepTabService, - /* eslint-disable @typescript-eslint/no-unsafe-assignment */ { provide: PathHelperService, useValue: pathHelper }, { provide: CurrentProjectService, useValue: currentProject }, - /* eslint-enable @typescript-eslint/no-unsafe-assignment */ ], }); diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-order.service.spec.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-order.service.spec.ts index 2c336a81a827..19fc98658d05 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-order.service.spec.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-order.service.spec.ts @@ -85,7 +85,6 @@ describe('WorkPackageViewOrderService', () => { id: '123', _links: { self: { href: 'test' } }, } as Record; - // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-argument querySpace.query.putValue(mockQuery as any); }); @@ -103,12 +102,10 @@ describe('WorkPackageViewOrderService', () => { const order = ['1', '2', '3']; const wpId = '2'; - // eslint-disable-next-line @typescript-eslint/no-explicit-any vi.spyOn(service as any, 'update'); service.remove(order, wpId); - // eslint-disable-next-line @typescript-eslint/no-explicit-any expect((service as any).update).toHaveBeenCalledWith({ [wpId]: -1 }); }); }); diff --git a/frontend/src/app/shared/components/primer/dynamic-icon.directive.spec.ts b/frontend/src/app/shared/components/primer/dynamic-icon.directive.spec.ts index 6b601814637d..2999b557a663 100644 --- a/frontend/src/app/shared/components/primer/dynamic-icon.directive.spec.ts +++ b/frontend/src/app/shared/components/primer/dynamic-icon.directive.spec.ts @@ -26,8 +26,6 @@ // See COPYRIGHT and LICENSE files for more details. //++ -/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any */ - import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DynamicIconDirective } from './dynamic-icon.directive'; diff --git a/frontend/src/app/shared/components/primer/icon-button.component.spec.ts b/frontend/src/app/shared/components/primer/icon-button.component.spec.ts index 0f2720b8fbe8..362fc510ab12 100644 --- a/frontend/src/app/shared/components/primer/icon-button.component.spec.ts +++ b/frontend/src/app/shared/components/primer/icon-button.component.spec.ts @@ -26,8 +26,6 @@ // See COPYRIGHT and LICENSE files for more details. //++ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ - import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PrimerIconButtonComponent } from './icon-button.component'; diff --git a/frontend/src/stimulus/controllers/checkable.controller.spec.ts b/frontend/src/stimulus/controllers/checkable.controller.spec.ts index 473e2081b203..891a796b3b42 100644 --- a/frontend/src/stimulus/controllers/checkable.controller.spec.ts +++ b/frontend/src/stimulus/controllers/checkable.controller.spec.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -/* eslint-disable @typescript-eslint/no-empty-function, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-empty-function */ import { ActionEvent } from '@hotwired/stimulus'; import CheckableController from './checkable.controller'; diff --git a/frontend/src/stimulus/controllers/dynamic/reporting/page.controller.spec.ts b/frontend/src/stimulus/controllers/dynamic/reporting/page.controller.spec.ts index 1690872b2587..0bc4003eb67c 100644 --- a/frontend/src/stimulus/controllers/dynamic/reporting/page.controller.spec.ts +++ b/frontend/src/stimulus/controllers/dynamic/reporting/page.controller.spec.ts @@ -26,8 +26,6 @@ // See COPYRIGHT and LICENSE files for more details. //++ -/* eslint-disable @typescript-eslint/no-explicit-any */ - import PageController from './page.controller'; describe('Reporting PageController serialization', () => { diff --git a/frontend/src/stimulus/controllers/expandable-text.controller.spec.ts b/frontend/src/stimulus/controllers/expandable-text.controller.spec.ts index 69990742f869..35700ed355d2 100644 --- a/frontend/src/stimulus/controllers/expandable-text.controller.spec.ts +++ b/frontend/src/stimulus/controllers/expandable-text.controller.spec.ts @@ -26,8 +26,6 @@ // See COPYRIGHT and LICENSE files for more details. //++ -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment */ - import ExpandableTextController from './expandable-text.controller'; import { setupStimulusTest, type StimulusTestContext } from 'core-stimulus/test-helpers'; diff --git a/frontend/src/test-setup.ts b/frontend/src/test-setup.ts index 2c83fff6175c..824a3b4d93ae 100644 --- a/frontend/src/test-setup.ts +++ b/frontend/src/test-setup.ts @@ -43,7 +43,6 @@ afterEach(() => { vi.useRealTimers(); }); -// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access (window as any).global = window; window.I18n = new I18n(); @@ -51,9 +50,7 @@ window.I18n = new I18n(); // jsdom does not implement CSS.escape; production helpers (e.g. getMetaElement) // call it unconditionally. if (typeof CSS === 'undefined' || typeof CSS.escape !== 'function') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).CSS = (globalThis as any).CSS || {}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access (globalThis as any).CSS.escape = (value:string) => String(value).replace(/[^a-zA-Z0-9_-]/g, (ch) => `\\${ch}`); } @@ -61,7 +58,6 @@ if (typeof CSS === 'undefined' || typeof CSS.escape !== 'function') { // constructor signature so static analysis resolving the global to this class // still accepts the callback every real call site passes. if (typeof (globalThis as any).ResizeObserver === 'undefined') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).ResizeObserver = class { // eslint-disable-next-line @typescript-eslint/no-empty-function constructor(_callback:ResizeObserverCallback) {} @@ -73,7 +69,6 @@ if (typeof (globalThis as any).ResizeObserver === 'undefined') { // jsdom does not implement HTMLDialogElement.showModal/close. if (typeof HTMLDialogElement !== 'undefined') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any const proto = HTMLDialogElement.prototype as any; if (typeof proto.showModal !== 'function') { proto.showModal = function showModal() { this.open = true; }; diff --git a/frontend/src/turbo/turbo-navigation-patch.spec.ts b/frontend/src/turbo/turbo-navigation-patch.spec.ts index 432bf784fc7d..5f4a4593370d 100644 --- a/frontend/src/turbo/turbo-navigation-patch.spec.ts +++ b/frontend/src/turbo/turbo-navigation-patch.spec.ts @@ -26,7 +26,6 @@ // See COPYRIGHT and LICENSE files for more details. //++ -/* eslint-disable @typescript-eslint/no-explicit-any */ import * as Turbo from '@hotwired/turbo'; import { applyTurboNavigationPatch } from './turbo-navigation-patch'; From a290bdf6484310cd4fe7039441d9810f42d7e63e Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 28 Sep 2026 22:08:19 +0100 Subject: [PATCH 2/5] [OP-20342] Remove unused global type declarations Removes the api.v3 Result, Collection and Duration interfaces, the Factory global and the op.QueryParams namespace. Nothing references them, and most declared their members as any. https://community.openproject.org/wp/OP-20342 --- .../src/typings/open-project.typings.d.ts | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/frontend/src/typings/open-project.typings.d.ts b/frontend/src/typings/open-project.typings.d.ts index cbffc738fbc8..e5afbccd2d39 100644 --- a/frontend/src/typings/open-project.typings.d.ts +++ b/frontend/src/typings/open-project.typings.d.ts @@ -38,24 +38,6 @@ declare namespace api { * API v3 */ namespace v3 { - interface Result { - _links:any; - _embedded:any; - _type:string; - } - - interface Collection extends Result { - total:number; - pageSize:number; - count:number; - offset:number; - groups:any; - totalSums:any; - } - - interface Duration extends String { - } - interface Formattable { format?:string; raw:string; @@ -74,15 +56,3 @@ interface Function { _type:string; } -declare let Factory:any; - -declare namespace op { - interface QueryParams { - offset?:number; - pageSize?:number; - filters?:any[]; - groupBy?:string; - showSums?:boolean; - sortBy?:any[]; - } -} From fc9401f32ac45eec53c0ce554c4f12dbe9bb4ab8 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 28 Sep 2026 22:08:20 +0100 Subject: [PATCH 3/5] [OP-20342] Type the tablesorter jQuery plugin Declares the tablesorter call and its language defaults, and the metadata plugin slot the reporting page clears, instead of any. The reporting controller no longer needs its inline disables for them. https://community.openproject.org/wp/OP-20342 --- .../controllers/dynamic/reporting/page.controller.ts | 2 -- frontend/src/typings/shims.d.ts | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/reporting/page.controller.ts b/frontend/src/stimulus/controllers/dynamic/reporting/page.controller.ts index 73dd9d7267ca..743df61d9a50 100644 --- a/frontend/src/stimulus/controllers/dynamic/reporting/page.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/reporting/page.controller.ts @@ -207,7 +207,6 @@ export default class PageController extends Controller { jQuery.metadata = undefined; // Override the default texts to enable translations - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access jQuery.tablesorter.language = { sortAsc: I18n.t('js.sort.sorted_asc'), sortDesc: I18n.t('js.sort.sorted_dsc'), @@ -218,7 +217,6 @@ export default class PageController extends Controller { nextNone: I18n.t('js.sort.activate_no'), }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call jQuery('#sortable-table') .not('.tablesorter') .tablesorter({ diff --git a/frontend/src/typings/shims.d.ts b/frontend/src/typings/shims.d.ts index ef00c32df1f8..f223098f11c5 100644 --- a/frontend/src/typings/shims.d.ts +++ b/frontend/src/typings/shims.d.ts @@ -69,12 +69,12 @@ declare global { } interface JQuery { - tablesorter:any; + tablesorter(options:object):JQuery; } interface JQueryStatic { - metadata:any; - tablesorter:any; + metadata:unknown; + tablesorter:{ language:Record }; } } From f760e37a21004ff653be7e12f6d265b910eca5ca Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 28 Sep 2026 22:10:34 +0100 Subject: [PATCH 4/5] [OP-20342] Type hook callbacks by hook name Adds a HookSignatures map for the hooks the core registers and calls, so register and call check callback parameters and return typed results instead of any. Arbitrary hook names still fall back to an open signature, because plugins register and call hooks by name. The fallback excludes the known names, so a mismatched callback or argument for a known hook fails to compile; the spec pins that down. The bulk context menu actions are now declared as WorkPackageAction, which the typed hook result exposed as narrower than its use. https://community.openproject.org/wp/OP-20342 --- .../app/features/plugins/hook-service.spec.ts | 12 +++++ .../src/app/features/plugins/hook-service.ts | 46 ++++++++++++++++--- .../wp-context-menu-helper.service.ts | 2 +- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/features/plugins/hook-service.spec.ts b/frontend/src/app/features/plugins/hook-service.spec.ts index 59d90d27796c..1be9dda6c7ef 100644 --- a/frontend/src/app/features/plugins/hook-service.spec.ts +++ b/frontend/src/app/features/plugins/hook-service.spec.ts @@ -156,4 +156,16 @@ describe('HookService', () => { shouldBehaveLikeResultWithElements(validId, 2); }); }); + + describe('known hooks', () => { + it('rejects callbacks and arguments that break the hook signature', () => { + // @ts-expect-error gridWidgets callbacks return widget registrations + service.register('gridWidgets', () => 123); + + // @ts-expect-error prependedAttributeGroups is called with a work package + service.call('prependedAttributeGroups', 'not a work package'); + + expect(service.call('gridWidgets')).toEqual([123]); + }); + }); }); diff --git a/frontend/src/app/features/plugins/hook-service.ts b/frontend/src/app/features/plugins/hook-service.ts index 7c5971862439..5c28c263b7e6 100644 --- a/frontend/src/app/features/plugins/hook-service.ts +++ b/frontend/src/app/features/plugins/hook-service.ts @@ -26,15 +26,43 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, Type } from '@angular/core'; +import type { HalResource } from 'core-app/features/hal/resources/hal-resource'; +import type { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; +import type { WorkPackageChangeset } from 'core-app/features/work-packages/components/wp-edit/work-package-changeset'; +import type { GroupDescriptor } from 'core-app/features/work-packages/components/wp-single-view/wp-single-view.component'; +import type { WorkPackageAction } from 'core-app/features/work-packages/components/wp-table/context-menu-helper/wp-context-menu-helper.service'; +import type { ResourceChangeset } from 'core-app/shared/components/fields/changeset/resource-changeset'; +import type { WidgetRegistration } from 'core-app/shared/components/grids/grid/grid.component'; + +type ResourceChangesetClass = new (...params:ConstructorParameters) => ResourceChangeset; + +export interface HookSignatures { + attributeGroupComponent:(group:GroupDescriptor, workPackage:WorkPackageResource) => Type|null; + gridWidgets:() => WidgetRegistration[]; + halResourceChangesetClass:(resource:HalResource) => ResourceChangesetClass|null; + prependedAttributeGroups:(workPackage:WorkPackageResource) => Type|undefined; + workPackageAttachmentListComponent:(workPackage:WorkPackageResource) => Type; + workPackageAttachmentUploadComponent:(workPackage:WorkPackageResource) => Type; + workPackageBulkContextMenu:() => WorkPackageAction; + workPackageNewInitialization:(change:WorkPackageChangeset) => void; + workPackageSingleContextMenu:() => WorkPackageAction; + workPackageTableContextMenu:() => WorkPackageAction; +} + +type HookCallback = (...params:never[]) => unknown; + +type CustomHookId = K extends keyof HookSignatures ? never : K; @Injectable({ providedIn: 'root', }) export class HookService { - private hooks:Record = {}; + private hooks:Record = {}; - public register(id:string, callback:Function) { + public register(id:K, callback:HookSignatures[K]):void; + public register(id:CustomHookId, callback:HookCallback):void; + public register(id:string, callback:HookCallback) { if (!callback) { return; } @@ -46,12 +74,18 @@ export class HookService { this.hooks[id].push(callback); } - public call(id:string, ...params:any[]):any[] { + public call( + id:K, + ...params:Parameters + ):NonNullable>[]; + + public call(id:CustomHookId, ...params:unknown[]):unknown[]; + public call(id:string, ...params:unknown[]):unknown[] { const results = []; if (this.hooks[id]) { - for (let x = 0; x < this.hooks[id].length; x++) { - const result = this.hooks[id][x](...params); + for (const hook of this.hooks[id] as ((...params:unknown[]) => unknown)[]) { + const result = hook(...params); if (result) { results.push(result); diff --git a/frontend/src/app/features/work-packages/components/wp-table/context-menu-helper/wp-context-menu-helper.service.ts b/frontend/src/app/features/work-packages/components/wp-table/context-menu-helper/wp-context-menu-helper.service.ts index 68d82621f843..15438eccfc9d 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/context-menu-helper/wp-context-menu-helper.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/context-menu-helper/wp-context-menu-helper.service.ts @@ -56,7 +56,7 @@ export class WorkPackageContextMenuHelperService { private wpViewIndent = inject(WorkPackageViewHierarchyIdentationService); private PathHelper = inject(PathHelperService); - private BULK_ACTIONS = [ + private BULK_ACTIONS:WorkPackageAction[] = [ { text: I18n.t('js.work_packages.bulk_actions.edit'), key: 'edit', From 661dab839dec1955d61aba4cbb934a6b6281f30f Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 28 Sep 2026 22:11:56 +0100 Subject: [PATCH 5/5] [OP-20342] Replace Function types with signatures Types the dynamic component output maps with ng-dynamic-component's EventHandler, which is what ndcDynamicOutputs accepts, and the checksum callback as a plain thunk. The unconstrained Function type accepted any callable and let calls through unchecked. https://community.openproject.org/wp/OP-20342 --- .../components/wp-list/wp-list-checksum.service.ts | 2 +- .../partitioned-query-space-page.component.ts | 3 ++- .../select-edit-field/select-edit-field.component.ts | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts index 805ef8378c5a..a17899f6320f 100644 --- a/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts @@ -116,7 +116,7 @@ export class WorkPackagesListChecksumService { public executeIfOutdated(newId:string|null, newChecksum:string|null, - callback:Function) { + callback:() => void) { if (this.isUninitialized() || this.isOutdated(newId, newChecksum)) { this.set(newId, newChecksum); diff --git a/frontend/src/app/features/work-packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.ts b/frontend/src/app/features/work-packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.ts index 755f6119a5a9..e9ad9b1086d4 100644 --- a/frontend/src/app/features/work-packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.ts +++ b/frontend/src/app/features/work-packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.ts @@ -49,11 +49,12 @@ import { firstValueFrom } from 'rxjs'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; import { UrlParamsService } from 'core-app/core/navigation/url-params.service'; +import { EventHandler } from 'ng-dynamic-component'; export interface DynamicComponentDefinition { component:ComponentType; inputs?:Record; - outputs?:Record; + outputs?:Record; } export interface ToolbarButtonComponentDefinition extends DynamicComponentDefinition { diff --git a/frontend/src/app/shared/components/fields/edit/field-types/select-edit-field/select-edit-field.component.ts b/frontend/src/app/shared/components/fields/edit/field-types/select-edit-field/select-edit-field.component.ts index 7a30c6b1eadf..34ef43e5c462 100644 --- a/frontend/src/app/shared/components/fields/edit/field-types/select-edit-field/select-edit-field.component.ts +++ b/frontend/src/app/shared/components/fields/edit/field-types/select-edit-field/select-edit-field.component.ts @@ -42,6 +42,7 @@ import { HalResourceNotificationService } from 'core-app/features/hal/services/h import { HalResourceSortingService } from 'core-app/features/hal/services/hal-resource-sorting.service'; import { EditFieldComponent } from '../../edit-field.component'; import { HalLink } from 'core-app/features/hal/hal-link/hal-link'; +import { EventHandler } from 'ng-dynamic-component'; export interface ValueOption { name:string; @@ -66,7 +67,7 @@ export class SelectEditFieldComponent extends EditFieldComponent implements OnIn public appendTo:any = null; - public referenceOutputs:Record = { + public referenceOutputs:Record = { onCreate: (newElement:HalResource) => this.onCreate(newElement), onChange: (value:HalResource) => this.onChange(value), onAddNew: (value:HalResource) => this.onNewValueAdded(value),