diff --git a/frontend/src/app/components/work-packages/work-package-comment/wp-comment-field.component.ts b/frontend/src/app/components/work-packages/work-package-comment/wp-comment-field.component.ts index dca28cfb2f..ad92253a07 100644 --- a/frontend/src/app/components/work-packages/work-package-comment/wp-comment-field.component.ts +++ b/frontend/src/app/components/work-packages/work-package-comment/wp-comment-field.component.ts @@ -39,7 +39,7 @@ import { export class WorkPackageCommentFieldComponent extends FormattableEditFieldComponent implements OnInit { public isBusy:boolean = false; - public ConfigurationService:ConfigurationService = this.$injector.get(ConfigurationService); + public ConfigurationService:ConfigurationService = this.injector.get(ConfigurationService); public get name() { return 'comment'; diff --git a/frontend/src/app/components/work-packages/wp-single-view/wp-single-view.component.ts b/frontend/src/app/components/work-packages/wp-single-view/wp-single-view.component.ts index 0cc238eb7e..6718123667 100644 --- a/frontend/src/app/components/work-packages/wp-single-view/wp-single-view.component.ts +++ b/frontend/src/app/components/work-packages/wp-single-view/wp-single-view.component.ts @@ -26,7 +26,7 @@ // See doc/COPYRIGHT.rdoc for more details. // ++ -import {Component, ElementRef, Inject, Input, OnDestroy, OnInit} from '@angular/core'; +import {Component, ElementRef, Inject, Injector, Input, OnDestroy, OnInit} from '@angular/core'; import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper.service'; import {componentDestroyed} from 'ng2-rx-componentdestroyed'; @@ -126,6 +126,7 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy { protected displayFieldService:DisplayFieldService, protected wpCacheService:WorkPackageCacheService, protected hook:HookService, + protected injector:Injector, readonly elementRef:ElementRef) { } @@ -356,7 +357,7 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy { resource, name, resource.schema[name], - { container: 'single-view', options: {} } + { container: 'single-view', injector: this.injector, options: {} } ) as DisplayField; } diff --git a/frontend/src/app/components/wp-edit-form/display-field-renderer.ts b/frontend/src/app/components/wp-edit-form/display-field-renderer.ts index cd8db9acca..7d9a6ffc39 100644 --- a/frontend/src/app/components/wp-edit-form/display-field-renderer.ts +++ b/frontend/src/app/components/wp-edit-form/display-field-renderer.ts @@ -79,7 +79,7 @@ export class DisplayFieldRenderer { } private getFieldForCurrentContext(workPackage:WorkPackageResource, fieldSchema:IFieldSchema, name:string):DisplayField { - const context:DisplayFieldContext = { container: this.container, options: this.options }; + const context:DisplayFieldContext = { container: this.container, injector: this.injector, options: this.options }; // We handle multi value fields differently in the single view context const isMultiLinesField = ['[]CustomOption', '[]User'].indexOf(fieldSchema.type) >= 0; diff --git a/frontend/src/app/components/wp-query-select/wp-query-select-dropdown.component.ts b/frontend/src/app/components/wp-query-select/wp-query-select-dropdown.component.ts index d18e1f1a69..40ccfea96d 100644 --- a/frontend/src/app/components/wp-query-select/wp-query-select-dropdown.component.ts +++ b/frontend/src/app/components/wp-query-select/wp-query-select-dropdown.component.ts @@ -118,8 +118,7 @@ export class WorkPackageQuerySelectDropdownComponent implements OnInit, OnDestro readonly loadingIndicator:LoadingIndicatorService, readonly pathHelper:PathHelperService, readonly wpStaticQueries:WorkPackageStaticQueriesService, - readonly toggleService:MainMenuToggleService, - readonly wpStatesInitialization:WorkPackageStatesInitializationService) { + readonly toggleService:MainMenuToggleService) { } public ngOnInit() { @@ -440,7 +439,6 @@ export class WorkPackageQuerySelectDropdownComponent implements OnInit, OnDestro // Ensure we're reloading the query if (isSameItem) { - this.wpStatesInitialization.clearStates(); this.wpListChecksumService.clear(); opts.reload = true; } diff --git a/frontend/src/app/components/wp-table/wp-table-sums-row/wp-table-sums-row.directive.ts b/frontend/src/app/components/wp-table/wp-table-sums-row/wp-table-sums-row.directive.ts index ed0959d601..eb42cee333 100644 --- a/frontend/src/app/components/wp-table/wp-table-sums-row/wp-table-sums-row.directive.ts +++ b/frontend/src/app/components/wp-table/wp-table-sums-row/wp-table-sums-row.directive.ts @@ -121,7 +121,12 @@ export class WorkPackageTableSumsRowController implements AfterViewInit { return div; } - const field = this.displayFieldService.getField(sums, name, fieldSchema, { container: 'table', options: {} }); + const field = this.displayFieldService.getField( + sums, + name, + fieldSchema, + { injector: this.injector, container: 'table', options: {} } + ); if (!field.isEmpty()) { field.render(div, field.valueString); diff --git a/frontend/src/app/components/wp-table/wp-table.directive.ts b/frontend/src/app/components/wp-table/wp-table.directive.ts index 123c124186..d574b2ad9e 100644 --- a/frontend/src/app/components/wp-table/wp-table.directive.ts +++ b/frontend/src/app/components/wp-table/wp-table.directive.ts @@ -107,7 +107,7 @@ export class WorkPackagesTableController implements OnInit, OnDestroy { public readonly uniqueTableIdentifier = `wp-table--container-${randomString(16)}`; constructor(readonly elementRef:ElementRef, - readonly injector:Injector, + readonly injector:Injector, readonly states:States, readonly querySpace:IsolatedQuerySpace, readonly opModalService:OpModalService, diff --git a/frontend/src/app/modules/fields/display/display-field.module.ts b/frontend/src/app/modules/fields/display/display-field.module.ts index c0ced3f78e..35904f0132 100644 --- a/frontend/src/app/modules/fields/display/display-field.module.ts +++ b/frontend/src/app/modules/fields/display/display-field.module.ts @@ -36,19 +36,28 @@ export class DisplayField extends Field { public mode:string | null = null; public changeset:WorkPackageChangeset|null = null; - protected I18n:I18nService + protected I18n:I18nService = this.$injector.get(I18nService); + constructor(public resource:any, public name:string, public schema:IFieldSchema, public context:DisplayFieldContext) { super(); - this.I18n = this.$injector.get(I18nService); } public get isFormattable():boolean { return false; } + /** + * Return the provided local injector, + * which is relevant to provide the display field + * the current space context. + */ + protected get $injector() { + return this.context.injector; + } + public get value() { if (!this.schema) { return null; diff --git a/frontend/src/app/modules/fields/display/display-field.service.ts b/frontend/src/app/modules/fields/display/display-field.service.ts index 982793b75d..0df4e3feaf 100644 --- a/frontend/src/app/modules/fields/display/display-field.service.ts +++ b/frontend/src/app/modules/fields/display/display-field.service.ts @@ -37,6 +37,9 @@ export interface IDisplayFieldType extends IFieldType { } export interface DisplayFieldContext { + /** The injector to use for the context of this field. Relevant for embedded service injection */ + injector:Injector; + /** Where will the field be rendered? This may result in different styles (Multi select field, e.g.,) */ container: 'table'|'single-view'|'timeline'; diff --git a/frontend/src/app/modules/fields/edit/field-types/duration-edit-field.component.ts b/frontend/src/app/modules/fields/edit/field-types/duration-edit-field.component.ts index 7b8892c93c..3bc5c75b7d 100644 --- a/frontend/src/app/modules/fields/edit/field-types/duration-edit-field.component.ts +++ b/frontend/src/app/modules/fields/edit/field-types/duration-edit-field.component.ts @@ -46,7 +46,7 @@ import {EditFieldComponent} from "core-app/modules/fields/edit/edit-field.compon ` }) export class DurationEditFieldComponent extends EditFieldComponent { - readonly TimezoneService:TimezoneService = this.$injector.get(TimezoneService); + readonly TimezoneService:TimezoneService = this.injector.get(TimezoneService); public parser(value:any) { if (!isNaN(value)) { diff --git a/frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.component.ts b/frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.component.ts index 08379e342d..f6ae90c325 100644 --- a/frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.component.ts +++ b/frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.component.ts @@ -57,7 +57,7 @@ export const formattableFieldTemplate = ` template: formattableFieldTemplate }) export class FormattableEditFieldComponent extends EditFieldComponent implements OnInit { - readonly pathHelper:PathHelperService = this.$injector.get(PathHelperService); + readonly pathHelper:PathHelperService = this.injector.get(PathHelperService); public readonly field = this; diff --git a/frontend/src/app/modules/fields/field.base.ts b/frontend/src/app/modules/fields/field.base.ts index a9df6f4534..27b468e03d 100644 --- a/frontend/src/app/modules/fields/field.base.ts +++ b/frontend/src/app/modules/fields/field.base.ts @@ -40,7 +40,6 @@ export interface IFieldSchema { export class Field { public static type:string; - public static $injector:Injector; public resource:any; public name:string; public schema:IFieldSchema; @@ -77,8 +76,4 @@ export class Field { public get unknownAttribute():boolean { return this.isEmpty && !this.schema; } - - protected get $injector():Injector { - return (this.constructor as typeof Field).$injector; - } } diff --git a/frontend/src/app/modules/work_packages/openproject-work-packages.module.ts b/frontend/src/app/modules/work_packages/openproject-work-packages.module.ts index 299b0292f7..2ca9b93f90 100644 --- a/frontend/src/app/modules/work_packages/openproject-work-packages.module.ts +++ b/frontend/src/app/modules/work_packages/openproject-work-packages.module.ts @@ -56,7 +56,6 @@ import {WorkPackageTimelineHeaderController} from 'core-components/wp-table/time import {WorkPackageTableTimelineRelations} from 'core-components/wp-table/timeline/global-elements/wp-timeline-relations.directive'; import {WorkPackageTableTimelineStaticElements} from 'core-components/wp-table/timeline/global-elements/wp-timeline-static-elements.directive'; import {WorkPackageTableTimelineGrid} from 'core-components/wp-table/timeline/grid/wp-timeline-grid.directive'; -import {WorkPackageTableTimelineService} from 'core-components/wp-fast-table/state/wp-table-timeline.service'; import {WorkPackageTimelineButtonComponent} from 'core-components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component'; import {WorkPackageOverviewTabComponent} from 'core-components/wp-single-view-tabs/overview-tab/overview-tab.component'; import {WorkPackageStatusButtonComponent} from 'core-components/wp-buttons/wp-status-button/wp-status-button.component'; @@ -135,7 +134,6 @@ import {ExternalRelationQueryConfigurationService} from "core-components/wp-tabl import {WorkPackageStaticQueriesService} from 'core-components/wp-query-select/wp-static-queries.service'; import {WorkPackagesListInvalidQueryService} from 'core-components/wp-list/wp-list-invalid-query.service'; import {WorkPackageInlineCreateService} from 'core-components/wp-inline-create/wp-inline-create.service'; -import {WorkPackageRelationsService} from 'core-components/wp-relations/wp-relations.service'; import {WorkPackageCacheService} from 'core-components/work-packages/work-package-cache.service'; import {SchemaCacheService} from 'core-components/schemas/schema-cache.service'; import {WorkPackageContextMenuHelperService} from 'core-components/wp-table/context-menu-helper/wp-context-menu-helper.service'; @@ -191,25 +189,14 @@ import {WorkPackageIsolatedQuerySpaceDirective} from "core-app/modules/work_pack multi: true }, - // Timeline - WorkPackageTableTimelineService, - // External query configuration ExternalQueryConfigurationService, ExternalRelationQueryConfigurationService, // Global work package states / services - WorkPackageService, WorkPackageCacheService, SchemaCacheService, - // Provide a separate service for creation events of WP Inline create - // This can be hierarchically injected to provide isolated events on an embedded table - WorkPackageInlineCreateService, - WpChildrenInlineCreateService, - WpRelationInlineCreateService, - WorkPackageRelationsService, - // Global query/table state services // For any service that depends on the isolated query space, // they should be provided in wp-isolated-query-space.directive instead @@ -220,15 +207,11 @@ import {WorkPackageIsolatedQuerySpaceDirective} from "core-app/modules/work_pack KeepTabService, WorkPackageNotificationService, WorkPackagesListChecksumService, - WorkPackageRelationsHierarchyService, - WorkPackageFiltersService, ApiWorkPackagesService, WorkPackagesActivityService, WorkPackageWatchersService, - WorkPackageContextMenuHelperService, - QueryFormDmService, ], declarations: [ diff --git a/frontend/src/app/modules/work_packages/query-space/wp-isolated-query-space.directive.ts b/frontend/src/app/modules/work_packages/query-space/wp-isolated-query-space.directive.ts index 6960717d90..67b828ba6b 100644 --- a/frontend/src/app/modules/work_packages/query-space/wp-isolated-query-space.directive.ts +++ b/frontend/src/app/modules/work_packages/query-space/wp-isolated-query-space.directive.ts @@ -26,7 +26,7 @@ // See doc/COPYRIGHT.rdoc for more details. // ++ -import {Directive} from '@angular/core'; +import {Directive, Injector} from '@angular/core'; import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space"; import {OpTableActionsService} from "core-components/wp-table/table-actions/table-actions.service"; import {WorkPackageTableRelationColumnsService} from "core-components/wp-fast-table/state/wp-table-relation-columns.service"; @@ -51,6 +51,14 @@ import {ReorderQueryService} from "core-app/modules/boards/drag-and-drop/reorder import {IWorkPackageEditingServiceToken} from "core-components/wp-edit-form/work-package-editing.service.interface"; import {WorkPackageEditingService} from "core-components/wp-edit-form/work-package-editing-service"; import {WorkPackagesListService} from "core-components/wp-list/wp-list.service"; +import {WorkPackageService} from "core-components/work-packages/work-package.service"; +import {WorkPackageRelationsService} from "core-components/wp-relations/wp-relations.service"; +import {WorkPackageRelationsHierarchyService} from "core-components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service"; +import {WorkPackageFiltersService} from "core-components/filters/wp-filters/wp-filters.service"; +import {WorkPackageContextMenuHelperService} from "core-components/wp-table/context-menu-helper/wp-context-menu-helper.service"; +import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; +import {WpChildrenInlineCreateService} from "core-components/wp-relations/embedded/children/wp-children-inline-create.service"; +import {WpRelationInlineCreateService} from "core-components/wp-relations/embedded/relations/wp-relation-inline-create.service"; /** * Directive to open a work package query 'space', an isolated injector hierarchy @@ -82,6 +90,17 @@ import {WorkPackagesListService} from "core-components/wp-list/wp-list.service"; WorkPackageTableRefreshService, WorkPackageTableFocusService, WorkPackageTableHighlightingService, + WorkPackageService, + WorkPackageRelationsService, + WorkPackageRelationsHierarchyService, + WorkPackageFiltersService, + WorkPackageContextMenuHelperService, + + // Provide a separate service for creation events of WP Inline create + // This can be hierarchically injected to provide isolated events on an embedded table + WorkPackageInlineCreateService, + WpChildrenInlineCreateService, + WpRelationInlineCreateService, // Provide both serves with tokens to avoid tight dependency cycles { provide: IWorkPackageCreateServiceToken, useClass: WorkPackageCreateService }, @@ -93,4 +112,8 @@ import {WorkPackagesListService} from "core-components/wp-list/wp-list.service"; ] }) export class WorkPackageIsolatedQuerySpaceDirective { + + constructor(private injector:Injector) { + console.log(injector.get(IsolatedQuerySpace)); + } } diff --git a/frontend/src/app/modules/work_packages/routing/work-packages-routes.ts b/frontend/src/app/modules/work_packages/routing/work-packages-routes.ts index e92df20e03..5a78e45940 100644 --- a/frontend/src/app/modules/work_packages/routing/work-packages-routes.ts +++ b/frontend/src/app/modules/work_packages/routing/work-packages-routes.ts @@ -50,7 +50,7 @@ export const WORK_PACKAGES_ROUTES:Ng2StateDeclaration[] = [ parent: 'root', component: WorkPackagesBaseComponent, url: '/work_packages?query_id&query_props', - abstract: true, + redirectTo: 'work-packages.list', params: { query_id: { type: 'query', dynamic: true }, // Use custom encoder/decoder that ensures validity of URL string diff --git a/frontend/src/app/modules/work_packages/routing/wp-base/wp--base.component.ts b/frontend/src/app/modules/work_packages/routing/wp-base/wp--base.component.ts index 30b0fbe20e..c5ba43513f 100644 --- a/frontend/src/app/modules/work_packages/routing/wp-base/wp--base.component.ts +++ b/frontend/src/app/modules/work_packages/routing/wp-base/wp--base.component.ts @@ -26,7 +26,7 @@ // See doc/COPYRIGHT.rdoc for more details. // ++ -import {Component} from "@angular/core"; +import {Component, Injector} from "@angular/core"; import {DynamicBootstrapper} from "core-app/globals/dynamic-bootstrapper"; export const wpBaseSelector = 'work-packages-base';