From c19924065ca5a51cd8d5c605e9261cba13da3284 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 4 Oct 2024 18:30:19 +0300 Subject: [PATCH] UI: Remove deprecated ComponentFactory usage. Improve typings in tsx files. --- ui-ngx/.eslintrc.json | 3 +- .../src/app/core/http/rule-chain.service.ts | 23 +- .../dynamic-component-factory.service.ts | 6 +- .../app/core/services/resources.service.ts | 209 +++++++++++------- ui-ngx/src/app/modules/common/modules-map.ts | 6 +- .../states/states-component.directive.ts | 3 +- .../states/states-controller.service.ts | 9 +- .../entity/add-entity-dialog.component.ts | 6 +- .../entity/entities-table.component.ts | 5 +- .../entity/entity-details-page.component.ts | 5 +- .../entity/entity-details-panel.component.ts | 10 +- .../add-device-profile-dialog.component.ts | 19 +- .../profile/asset-profile-dialog.component.ts | 14 +- .../device-profile-dialog.component.ts | 14 +- .../tenant-profile-dialog.component.ts | 14 +- .../config/widget-settings.component.ts | 16 +- .../widget/dialog/custom-dialog.service.ts | 14 +- .../widget/widget-component.service.ts | 58 ++--- .../widget/widget-config.component.ts | 5 +- .../components/widget/widget.component.ts | 45 ++-- .../rulechain/rule-node-config.component.ts | 22 +- .../directives/component-outlet.directive.ts | 14 +- .../json-form/react/json-form-ace-editor.tsx | 8 +- .../json-form/react/json-form-array.tsx | 6 +- .../react/json-form-base-component.tsx | 2 +- .../json-form/react/json-form-checkbox.tsx | 2 +- .../json-form/react/json-form-color.tsx | 2 +- .../json-form/react/json-form-css.tsx | 2 +- .../json-form/react/json-form-date.tsx | 2 +- .../json-form/react/json-form-html.tsx | 2 +- .../json-form/react/json-form-icon.tsx | 11 +- .../json-form/react/json-form-image.tsx | 12 +- .../json-form/react/json-form-javascript.tsx | 2 +- .../json-form/react/json-form-json.tsx | 2 +- .../json-form/react/json-form-markdown.tsx | 2 +- .../json-form/react/json-form-number.tsx | 9 +- .../json-form/react/json-form-rc-select.tsx | 6 +- .../json-form/react/json-form-schema-form.tsx | 16 +- .../json-form/react/json-form-select.tsx | 6 +- .../json-form/react/json-form-text.tsx | 2 +- .../shared/components/popover.component.ts | 13 +- .../app/shared/components/popover.service.ts | 22 +- .../app/shared/components/toast.directive.ts | 6 +- ui-ngx/src/app/shared/models/widget.models.ts | 2 +- 44 files changed, 308 insertions(+), 349 deletions(-) diff --git a/ui-ngx/.eslintrc.json b/ui-ngx/.eslintrc.json index 67c9f91645..598c8074a6 100644 --- a/ui-ngx/.eslintrc.json +++ b/ui-ngx/.eslintrc.json @@ -6,7 +6,8 @@ "overrides": [ { "files": [ - "*.ts" + "*.ts", + "*.tsx" ], "parserOptions": { "project": [ diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index 15cd79c2e3..e3353989cc 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ComponentFactory, Injectable } from '@angular/core'; +import { Injectable, Type } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { forkJoin, Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; @@ -31,11 +31,13 @@ import { ComponentDescriptorService } from './component-descriptor.service'; import { IRuleNodeConfigurationComponent, LinkLabel, - RuleNodeComponentDescriptor, RuleNodeConfiguration, ScriptLanguage, + RuleNodeComponentDescriptor, + RuleNodeConfiguration, + ScriptLanguage, TestScriptInputParams, TestScriptResult } from '@app/shared/models/rule-node.models'; -import { ResourcesService } from '../services/resources.service'; +import { componentTypeBySelector, ResourcesService } from '../services/resources.service'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { deepClone, snakeCase } from '@core/utils'; @@ -50,7 +52,7 @@ export class RuleChainService { private ruleNodeComponentsMap: Map> = new Map>(); - private ruleNodeConfigFactories: {[directive: string]: ComponentFactory} = {}; + private ruleNodeConfigComponents: {[directive: string]: Type} = {}; constructor( private http: HttpClient, @@ -126,8 +128,8 @@ export class RuleChainService { } } - public getRuleNodeConfigFactory(directive: string): ComponentFactory { - return this.ruleNodeConfigFactories[directive]; + public getRuleNodeConfigComponent(directive: string): Type { + return this.ruleNodeConfigComponents[directive]; } public getRuleNodeComponentByClazz(ruleChainType: RuleChainType = RuleChainType.CORE, clazz: string): RuleNodeComponentDescriptor { @@ -219,14 +221,13 @@ export class RuleChainService { }); } if (moduleResource) { - tasks.push(this.resourcesService.loadFactories(moduleResource, modulesMap).pipe( + tasks.push(this.resourcesService.loadModulesWithComponents(moduleResource, modulesMap).pipe( map((res) => { if (nodeDefinition.configDirective && nodeDefinition.configDirective.length) { const selector = snakeCase(nodeDefinition.configDirective, '-'); - const componentFactory = res.factories.find((factory) => - factory.selector === selector); - if (componentFactory) { - this.ruleNodeConfigFactories[nodeDefinition.configDirective] = componentFactory; + const componentType = componentTypeBySelector(res, selector); + if (componentType) { + this.ruleNodeConfigComponents[nodeDefinition.configDirective] = componentType; } else { component.configurationDescriptor.nodeDefinition.uiResourceLoadError = this.translate.instant('rulenode.directive-is-not-loaded', diff --git a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts index 597f290480..40ac9c354b 100644 --- a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts +++ b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts @@ -31,14 +31,14 @@ export class DynamicComponentFactoryService { public createDynamicComponent( componentType: Type, template: string, - modules?: Type[], + imports?: Type[], preserveWhitespaces?: boolean, styles?: string[]): Observable> { return from(import('@angular/compiler')).pipe( mergeMap(() => { let componentImports: Type[] = [CommonModule]; - if (modules) { - componentImports = [...componentImports, ...modules]; + if (imports) { + componentImports = [...componentImports, ...imports]; } const comp = this.createAndCompileDynamicComponent(componentType, template, componentImports, preserveWhitespaces, styles); return of(comp.type); diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index 013c3e5d72..3e45ad0d3c 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -15,16 +15,19 @@ /// import { - Compiler, - ComponentFactory, + createNgModule, Inject, Injectable, Injector, - ModuleWithComponentFactories, - Type, ɵNG_MOD_DEF + Type, + ɵComponentDef, + ɵCssSelectorList, + ɵNG_COMP_DEF, + ɵNG_MOD_DEF, + ɵNgModuleDef } from '@angular/core'; import { DOCUMENT } from '@angular/common'; -import { forkJoin, Observable, ReplaySubject, throwError } from 'rxjs'; +import { Observable, ReplaySubject, throwError } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { IModulesMap } from '@modules/common/modules-map.models'; import { TbResourceId } from '@shared/models/id/tb-resource-id'; @@ -36,13 +39,56 @@ import { AppState } from '@core/core.state'; import { map, tap } from 'rxjs/operators'; import { RequestConfig } from '@core/http/http-utils'; -declare const System; +export interface ModuleInfo { + module: ɵNgModuleDef; + components: ɵComponentDef[]; +} + +export interface ModulesWithComponents { + modules: ModuleInfo[]; + standaloneComponents: ɵComponentDef[]; +} + +export const flatModulesWithComponents = (modulesWithComponentsList: ModulesWithComponents[]): ModulesWithComponents => { + const modulesWithComponents: ModulesWithComponents = { + modules: [], + standaloneComponents: [] + }; + for (const m of modulesWithComponentsList) { + for (const module of m.modules) { + if (!modulesWithComponents.modules.some(m1 => m1.module === module.module)) { + modulesWithComponents.modules.push(module); + } + } + for (const comp of m.standaloneComponents) { + if (!modulesWithComponents.standaloneComponents.includes(comp)) { + modulesWithComponents.standaloneComponents.push(comp); + } + } + } + return modulesWithComponents; +} + +export const modulesWithComponentsToTypes = (modulesWithComponents: ModulesWithComponents): Type[] => + [...modulesWithComponents.modules.map(m => m.module.type), + ...modulesWithComponents.standaloneComponents.map(c => c.type)]; -export interface ModulesWithFactories { - modules: Type[]; - factories: ComponentFactory[]; +export const componentTypeBySelector = (modulesWithComponents: ModulesWithComponents, selector: string): Type | undefined => { + let found = modulesWithComponents.standaloneComponents.find(c => matchesSelector(c.selectors, selector)); + if (!found) { + for (const m of modulesWithComponents.modules) { + found = m.components.find(c => matchesSelector(c.selectors, selector)); + if (found) { + break; + } + } + } + return found?.type; } +const matchesSelector = (selectors: ɵCssSelectorList, selector: string) => + selectors.some(s => s.some(s1 => typeof s1 === 'string' && s1 === selector)); + @Injectable({ providedIn: 'root' }) @@ -50,16 +96,15 @@ export class ResourcesService { private loadedJsonResources: { [url: string]: ReplaySubject } = {}; private loadedResources: { [url: string]: ReplaySubject } = {}; - private loadedModulesAndFactories: { [url: string]: ReplaySubject } = {}; + private loadedModulesWithComponents: { [url: string]: ReplaySubject } = {}; private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0]; - constructor(@Inject(DOCUMENT) private readonly document: any, + constructor(@Inject(DOCUMENT) private readonly document: Document, protected store: Store, - private compiler: Compiler, private http: HttpClient, private injector: Injector) { - this.store.pipe(select(selectIsAuthenticated)).subscribe(() => this.clearModulesCache()); + this.store.pipe(select(selectIsAuthenticated)).subscribe(() => this.clearModulesWithComponentsCache()); } public loadJsonResource(url: string, postProcess?: (data: T) => T): Observable { @@ -93,7 +138,7 @@ export class ResourcesService { return this.loadedResources[url].asObservable(); } - let fileType; + let fileType: string; const match = /[./](css|less|html|htm|js)?(([?#]).*)?$/.exec(url); if (match !== null) { fileType = match[1]; @@ -137,53 +182,38 @@ export class ResourcesService { ); } - public loadFactories(resourceId: string | TbResourceId, modulesMap: IModulesMap): Observable { + public loadModulesWithComponents(resourceId: string | TbResourceId, modulesMap: IModulesMap): Observable { const url = this.getDownloadUrl(resourceId); - if (this.loadedModulesAndFactories[url]) { - return this.loadedModulesAndFactories[url].asObservable(); + if (this.loadedModulesWithComponents[url]) { + return this.loadedModulesWithComponents[url].asObservable(); } modulesMap.init(); const meta = this.getMetaInfo(resourceId); - const subject = new ReplaySubject(); - this.loadedModulesAndFactories[url] = subject; + const subject = new ReplaySubject(); + this.loadedModulesWithComponents[url] = subject; import('@angular/compiler').then( () => { + // @ts-ignore System.import(url, undefined, meta).then( - (module) => { - const modules = this.extractNgModules(module); - if (modules.length) { - const tasks: Promise>[] = []; - for (const m of modules) { - tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m)); - } - forkJoin(tasks).subscribe({ - next: (compiled) => { - try { - const componentFactories: ComponentFactory[] = []; - for (const c of compiled) { - c.ngModuleFactory.create(this.injector); - componentFactories.push(...c.componentFactories); - } - const modulesWithFactories: ModulesWithFactories = { - modules, - factories: componentFactories - }; - this.loadedModulesAndFactories[url].next(modulesWithFactories); - this.loadedModulesAndFactories[url].complete(); - } catch (e) { - this.loadedModulesAndFactories[url].error(new Error(`Unable to init module from url: ${url}`)); - } - }, - error: (e) => { - this.loadedModulesAndFactories[url].error(new Error(`Unable to compile module from url: ${url}`)); + (module: any) => { + try { + const modulesWithComponents = this.extractModulesWithComponents(module); + if (modulesWithComponents.modules.length || modulesWithComponents.standaloneComponents.length) { + for (const module of modulesWithComponents.modules) { + createNgModule(module.module.type, this.injector); } - }); - } else { - this.loadedModulesAndFactories[url].error(new Error(`Module '${url}' doesn't have default export!`)); + this.loadedModulesWithComponents[url].next(modulesWithComponents); + this.loadedModulesWithComponents[url].complete(); + } else { + this.loadedModulesWithComponents[url].error(new Error(`Module '${url}' doesn't have exported modules or components!`)); + } + } catch (e) { + console.log(`Unable to parse module from url: ${url}`, e); + this.loadedModulesWithComponents[url].error(new Error(`Unable to parse module from url: ${url}`)); } }, - (e) => { - this.loadedModulesAndFactories[url].error(new Error(`Unable to load module from url: ${url}`)); + () => { + this.loadedModulesWithComponents[url].error(new Error(`Unable to load module from url: ${url}`)); } ); } @@ -192,7 +222,7 @@ export class ResourcesService { tap({ next: () => System.delete(url), error: () => { - delete this.loadedModulesAndFactories[url]; + delete this.loadedModulesWithComponents[url]; System.delete(url); }, complete: () => System.delete(url) @@ -200,41 +230,66 @@ export class ResourcesService { ); } - private extractNgModules(module: any, modules: Type[] = []): Type[] { - try { - let potentialModules = [module]; - let currentScanDepth = 0; - while (potentialModules.length && currentScanDepth < 10) { - const newPotentialModules = []; - for (const potentialModule of potentialModules) { - if (potentialModule && (ɵNG_MOD_DEF in potentialModule)) { - modules.push(potentialModule); + private extractModulesWithComponents(module: any, + modulesWithComponents: ModulesWithComponents = { + modules: [], + standaloneComponents: [] + }, + visitedModules: Set = new Set()): ModulesWithComponents { + if (module && ['object', 'function'].includes(typeof module)) { + if (ɵNG_MOD_DEF in module) { + const moduleDef: ɵNgModuleDef = module[ɵNG_MOD_DEF]; + if (!visitedModules.has(moduleDef.type)) { + visitedModules.add(moduleDef.type); + const moduleInfo: ModuleInfo = { + module: moduleDef, + components: [] + } + modulesWithComponents.modules.push(moduleInfo); + const exportsDecl = moduleDef.exports; + let exports: Type[]; + if (Array.isArray(exportsDecl)) { + exports = exportsDecl; } else { - for (const k of Object.keys(potentialModule)) { - if (!this.isPrimitive(potentialModule[k])) { - newPotentialModules.push(potentialModule[k]); + exports = exportsDecl(); + } + for (const element of exports) { + if (ɵNG_COMP_DEF in element) { + const component: ɵComponentDef = element[ɵNG_COMP_DEF]; + if (!component.standalone) { + moduleInfo.components.push(component); + } else { + modulesWithComponents.standaloneComponents.push(component); } + } else { + this.extractModulesWithComponents(module, modulesWithComponents, visitedModules); } } } - potentialModules = newPotentialModules; - currentScanDepth++; + } else if (ɵNG_COMP_DEF in module) { + const component: ɵComponentDef = module[ɵNG_COMP_DEF]; + if (component.standalone) { + if (!modulesWithComponents.standaloneComponents.includes(component)) { + modulesWithComponents.standaloneComponents.push(component); + } + } + } else { + for (const k of Object.keys(module)) { + const val = module[k]; + if (val && ['object', 'function'].includes(typeof val)) { + this.extractModulesWithComponents(val, modulesWithComponents, visitedModules); + } + } } - } catch (e) { - console.log('Could not load NgModule', e); } - return modules; - } - - private isPrimitive(test) { - return test !== Object(test); + return modulesWithComponents; } private loadResourceByType(type: 'css' | 'js', url: string): Observable { const subject = new ReplaySubject(); this.loadedResources[url] = subject; - let el; + let el: any; let loaded = false; switch (type) { case 'js': @@ -250,7 +305,7 @@ export class ResourcesService { el.href = url; break; } - el.onload = el.onreadystatechange = (e) => { + el.onload = el.onreadystatechange = () => { if (el.readyState && !/^c|loade/.test(el.readyState) || loaded) { return; } el.onload = el.onreadystatechange = null; loaded = true; @@ -282,7 +337,7 @@ export class ResourcesService { } } - private clearModulesCache() { - this.loadedModulesAndFactories = {}; + private clearModulesWithComponentsCache() { + this.loadedModulesWithComponents = {}; } } diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index bd206528bb..76bc84bbec 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -339,8 +339,6 @@ import { TimezoneComponent } from '@shared/components/time/timezone.component'; import { TimezonePanelComponent } from '@shared/components/time/timezone-panel.component'; import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; -declare const System; - class ModulesMap implements IModulesMap { private initialized = false; @@ -673,7 +671,7 @@ class ModulesMap implements IModulesMap { init() { if (!this.initialized) { - System.constructor.prototype.resolve = (id) => { + System.constructor.prototype.resolve = (id: string) => { try { if (this.modulesMap[id]) { return 'app:' + id; @@ -688,7 +686,7 @@ class ModulesMap implements IModulesMap { System.set('app:' + moduleId, this.modulesMap[moduleId]); } System.constructor.prototype.shouldFetch = (url: string) => url.endsWith('/download'); - System.constructor.prototype.fetch = (url, options: RequestInit & {meta?: any}) => { + System.constructor.prototype.fetch = (url: string, options: RequestInit & {meta?: any}) => { if (options?.meta?.additionalHeaders) { options.headers = { ...options.headers, ...options.meta.additionalHeaders }; } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts index 9d413b7e81..f1f7313106 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts @@ -126,8 +126,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { } const stateControllerInstanceId = this.dashboardCtrl.dashboardCtx.instanceId + '_' + this.statesControllerId; const preservedState = this.statesControllerService.withdrawStateControllerState(stateControllerInstanceId); - const stateControllerFactory = stateControllerData.factory; - this.stateControllerComponentRef = this.viewContainerRef.createComponent(stateControllerFactory); + this.stateControllerComponentRef = this.viewContainerRef.createComponent(stateControllerData.component); this.stateControllerComponent = this.stateControllerComponentRef.instance; this.dashboardCtrl.dashboardCtx.stateController = this.stateControllerComponent; this.dashboardCtrl.dashboardCtx.stateChanged = this.stateChangedSubject.asObservable(); diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-controller.service.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-controller.service.ts index efa6c19f74..5c0619b832 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-controller.service.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-controller.service.ts @@ -14,12 +14,12 @@ /// limitations under the License. /// -import { ComponentFactory, ComponentFactoryResolver, Injectable, Type } from '@angular/core'; +import { Injectable, Type } from '@angular/core'; import { deepClone } from '@core/utils'; import { IStateControllerComponent } from '@home/components/dashboard-page/states/state-controller.models'; export interface StateControllerData { - factory: ComponentFactory; + component: Type; } @Injectable() @@ -29,13 +29,12 @@ export class StatesControllerService { statesControllerStates: {[stateControllerInstanceId: string]: any} = {}; - constructor(private componentFactoryResolver: ComponentFactoryResolver) { + constructor() { } public registerStatesController(stateControllerId: string, stateControllerComponent: Type): void { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(stateControllerComponent); this.statesControllers[stateControllerId] = { - factory: componentFactory + component: stateControllerComponent }; } diff --git a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts index 9fd6cd3792..224366a8cb 100644 --- a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, ComponentFactoryResolver, Inject, Injector, OnInit, SkipSelf, ViewChild } from '@angular/core'; +import { Component, Inject, Injector, OnInit, SkipSelf, ViewChild } from '@angular/core'; import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; @@ -55,7 +55,6 @@ export class AddEntityDialogComponent extends protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData>, public dialogRef: MatDialogRef>, - private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector, @SkipSelf() private errorStateMatcher: ErrorStateMatcher) { super(store, router, dialogRef); @@ -66,7 +65,6 @@ export class AddEntityDialogComponent extends this.translations = this.entitiesTableConfig.entityTranslations; this.resources = this.entitiesTableConfig.entityResources; this.entity = {}; - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityComponent); const viewContainerRef = this.entityDetailsFormAnchor.viewContainerRef; viewContainerRef.clear(); const injector: Injector = Injector.create( @@ -84,7 +82,7 @@ export class AddEntityDialogComponent extends parent: this.injector } ); - const componentRef = viewContainerRef.createComponent(componentFactory, 0, injector); + const componentRef = viewContainerRef.createComponent(this.entitiesTableConfig.entityComponent, {index: 0, injector}); this.entityComponent = componentRef.instance; this.entityComponent.isEdit = true; this.detailsForm = this.entityComponent.entityForm; diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts index 96b272f2d2..9bb5fe4d8a 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts @@ -19,7 +19,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ComponentFactoryResolver, ElementRef, EventEmitter, Input, NgZone, @@ -140,7 +139,6 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa private domSanitizer: DomSanitizer, private cd: ChangeDetectorRef, private router: Router, - private componentFactoryResolver: ComponentFactoryResolver, private elementRef: ElementRef, private fb: FormBuilder, private zone: NgZone) { @@ -193,10 +191,9 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa this.entitiesTableConfig = entitiesTableConfig; this.pageMode = this.entitiesTableConfig.pageMode; if (this.entitiesTableConfig.headerComponent) { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.headerComponent); const viewContainerRef = this.entityTableHeaderAnchor.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent(componentFactory); + const componentRef = viewContainerRef.createComponent(this.entitiesTableConfig.headerComponent); const headerComponent = componentRef.instance; headerComponent.entitiesTableConfig = this.entitiesTableConfig; } diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts index 6cce657cbb..baa46b135d 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts @@ -18,7 +18,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ComponentFactoryResolver, HostBinding, Injector, OnDestroy, @@ -30,7 +29,6 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod import { BaseData, HasId } from '@shared/models/base-data'; import { ActivatedRoute, Router } from '@angular/router'; import { UntypedFormGroup } from '@angular/forms'; -import { AssetId } from '@shared/models/id/asset-id'; import { TranslateService } from '@ngx-translate/core'; import { deepClone } from '@core/utils'; import { BroadcastService } from '@core/services/broadcast.service'; @@ -74,12 +72,11 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl private router: Router, protected injector: Injector, protected cd: ChangeDetectorRef, - protected componentFactoryResolver: ComponentFactoryResolver, private broadcast: BroadcastService, private translate: TranslateService, private dialogService: DialogService, protected store: Store) { - super(store, injector, cd, componentFactoryResolver); + super(store, injector, cd); this.entitiesTableConfig = this.route.snapshot.data.entitiesTableConfig; this.backNavigationCommands = this.route.snapshot.data.backNavigationCommands; } diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts index 1ec56028af..3c47058cfe 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts @@ -19,7 +19,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ComponentFactoryResolver, ComponentRef, EventEmitter, Injector, @@ -97,8 +96,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV constructor(protected store: Store, protected injector: Injector, - protected cd: ChangeDetectorRef, - protected componentFactoryResolver: ComponentFactoryResolver) { + protected cd: ChangeDetectorRef) { super(store); } @@ -167,7 +165,6 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV this.entityComponentRef.destroy(); this.entityComponentRef = null; } - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityComponent); const viewContainerRef = this.entityDetailsFormAnchor.viewContainerRef; viewContainerRef.clear(); const injector: Injector = Injector.create( @@ -185,7 +182,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV parent: this.injector } ); - this.entityComponentRef = viewContainerRef.createComponent(componentFactory, 0, injector); + this.entityComponentRef = viewContainerRef.createComponent(this.entitiesTableConfig.entityComponent, {index: 0, injector}); this.entityComponent = this.entityComponentRef.instance; this.entityComponent.isEdit = this.isEdit; this.detailsForm = this.entityComponent.entityForm; @@ -207,8 +204,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV viewContainerRef.clear(); this.entityTabsComponent = null; if (this.entitiesTableConfig.entityTabsComponent) { - const componentTabsFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityTabsComponent); - this.entityTabsComponentRef = viewContainerRef.createComponent(componentTabsFactory); + this.entityTabsComponentRef = viewContainerRef.createComponent(this.entitiesTableConfig.entityTabsComponent); this.entityTabsComponent = this.entityTabsComponentRef.instance; this.entityTabsComponent.isEdit = this.isEdit; this.entityTabsComponent.entitiesTableConfig = this.entitiesTableConfig; diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index e6a2a1c7fd..cee7426e26 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -14,16 +14,7 @@ /// limitations under the License. /// -import { - AfterViewInit, - Component, - ComponentFactoryResolver, - Inject, - Injector, - SkipSelf, - ViewChild -} from '@angular/core'; -import { ErrorStateMatcher } from '@angular/material/core'; +import { Component, Inject, ViewChild } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -68,7 +59,7 @@ export interface AddDeviceProfileDialogData { styleUrls: ['./add-device-profile-dialog.component.scss'] }) export class AddDeviceProfileDialogComponent extends - DialogComponent implements AfterViewInit { + DialogComponent { @ViewChild('addDeviceProfileStepper', {static: true}) addDeviceProfileStepper: MatStepper; stepperOrientation: Observable; @@ -105,10 +96,7 @@ export class AddDeviceProfileDialogComponent extends protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData, public dialogRef: MatDialogRef, - private componentFactoryResolver: ComponentFactoryResolver, - private injector: Injector, private breakpointObserver: BreakpointObserver, - @SkipSelf() private errorStateMatcher: ErrorStateMatcher, private deviceProfileService: DeviceProfileService, private fb: UntypedFormBuilder) { super(store, router, dialogRef); @@ -162,9 +150,6 @@ export class AddDeviceProfileDialogComponent extends {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); } - ngAfterViewInit(): void { - } - cancel(): void { this.dialogRef.close(null); } diff --git a/ui-ngx/src/app/modules/home/components/profile/asset-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/asset-profile-dialog.component.ts index 0c726bf3d6..e69b1cf2ce 100644 --- a/ui-ngx/src/app/modules/home/components/profile/asset-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/asset-profile-dialog.component.ts @@ -14,20 +14,12 @@ /// limitations under the License. /// -import { - AfterViewInit, - Component, - ComponentFactoryResolver, - Inject, - Injector, - SkipSelf, - ViewChild -} from '@angular/core'; +import { AfterViewInit, Component, Inject, SkipSelf, ViewChild } from '@angular/core'; import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { UntypedFormControl, FormGroupDirective, NgForm } from '@angular/forms'; +import { FormGroupDirective, NgForm, UntypedFormControl } from '@angular/forms'; import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; import { AssetProfile } from '@shared/models/asset.models'; @@ -59,8 +51,6 @@ export class AssetProfileDialogComponent extends protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogData, public dialogRef: MatDialogRef, - private componentFactoryResolver: ComponentFactoryResolver, - private injector: Injector, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, private assetProfileService: AssetProfileService) { super(store, router, dialogRef); diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.ts index 4622bc2d3a..27e4ff95f1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.ts @@ -14,20 +14,12 @@ /// limitations under the License. /// -import { - AfterViewInit, - Component, - ComponentFactoryResolver, - Inject, - Injector, - SkipSelf, - ViewChild -} from '@angular/core'; +import { AfterViewInit, Component, Inject, SkipSelf, ViewChild } from '@angular/core'; import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { UntypedFormControl, FormGroupDirective, NgForm } from '@angular/forms'; +import { FormGroupDirective, NgForm, UntypedFormControl } from '@angular/forms'; import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; import { DeviceProfile } from '@shared/models/device.models'; @@ -59,8 +51,6 @@ export class DeviceProfileDialogComponent extends protected router: Router, @Inject(MAT_DIALOG_DATA) public data: DeviceProfileDialogData, public dialogRef: MatDialogRef, - private componentFactoryResolver: ComponentFactoryResolver, - private injector: Injector, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, private deviceProfileService: DeviceProfileService) { super(store, router, dialogRef); diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.ts index 9face5403b..7a5453912d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.ts @@ -14,20 +14,12 @@ /// limitations under the License. /// -import { - AfterViewInit, - Component, - ComponentFactoryResolver, - Inject, - Injector, - SkipSelf, - ViewChild -} from '@angular/core'; +import { AfterViewInit, Component, Inject, SkipSelf, ViewChild } from '@angular/core'; import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { UntypedFormControl, FormGroupDirective, NgForm } from '@angular/forms'; +import { FormGroupDirective, NgForm, UntypedFormControl } from '@angular/forms'; import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; import { TenantProfile } from '@shared/models/tenant.model'; @@ -59,8 +51,6 @@ export class TenantProfileDialogComponent extends protected router: Router, @Inject(MAT_DIALOG_DATA) public data: TenantProfileDialogData, public dialogRef: MatDialogRef, - private componentFactoryResolver: ComponentFactoryResolver, - private injector: Injector, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, private tenantProfileService: TenantProfileService) { super(store, router, dialogRef); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts index e1ff910ab2..d8060d7d0e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts @@ -15,15 +15,12 @@ /// import { - AfterViewInit, Component, - ComponentFactoryResolver, ComponentRef, forwardRef, Input, OnChanges, OnDestroy, - OnInit, SimpleChanges, ViewChild, ViewContainerRef @@ -49,7 +46,6 @@ import { Dashboard } from '@shared/models/dashboard.models'; import { WidgetService } from '@core/http/widget.service'; import { IAliasController } from '@core/api/widget-api.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; -import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; @Component({ @@ -67,7 +63,7 @@ import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-con multi: true }] }) -export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, OnDestroy, AfterViewInit, OnChanges, Validator { +export class WidgetSettingsComponent implements ControlValueAccessor, OnDestroy, OnChanges, Validator { @ViewChild('definedSettingsContent', {read: ViewContainerRef, static: true}) definedSettingsContainer: ViewContainerRef; @@ -106,7 +102,6 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On private propagateChange = (_v: any) => { }; constructor(private translate: TranslateService, - private cfr: ComponentFactoryResolver, private widgetService: WidgetService, private fb: UntypedFormBuilder) { this.widgetSettingsFormGroup = this.fb.group({ @@ -121,9 +116,6 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On registerOnTouched(fn: any): void { } - ngOnInit(): void { - } - ngOnChanges(changes: SimpleChanges): void { for (const propName of Object.keys(changes)) { const change = changes[propName]; @@ -164,9 +156,6 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On } } - ngAfterViewInit(): void { - } - setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { @@ -232,8 +221,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On this.changeSubscription = null; } this.definedSettingsContainer.clear(); - const factory = this.cfr.resolveComponentFactory(componentType); - this.definedSettingsComponentRef = this.definedSettingsContainer.createComponent(factory); + this.definedSettingsComponentRef = this.definedSettingsContainer.createComponent(componentType); this.definedSettingsComponent = this.definedSettingsComponentRef.instance; this.definedSettingsComponent.aliasController = this.aliasController; this.definedSettingsComponent.callbacks = this.callbacks; diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts index 2705a8902c..3dac11f7b6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts @@ -35,7 +35,7 @@ import { @Injectable() export class CustomDialogService { - private customModules: Array>; + private customImports: Array>; constructor( private dynamicComponentFactoryService: DynamicComponentFactoryService, @@ -47,19 +47,19 @@ export class CustomDialogService { ) { } - setAdditionalModules(modules: Array>) { - this.customModules = modules; + setAdditionalImports(imports: Array>) { + this.customImports = imports; } customDialog(template: string, controller: (instance: CustomDialogComponent) => void, data?: any, config?: MatDialogConfig): Observable { - const modules = [this.sharedModule, CommonModule, this.sharedHomeComponentsModule, this.homeComponentsModule, + const imports = [this.sharedModule, CommonModule, this.sharedHomeComponentsModule, this.homeComponentsModule, this.widgetComponentsModule]; - if (Array.isArray(this.customModules)) { - modules.push(...this.customModules); + if (Array.isArray(this.customImports)) { + imports.push(...this.customImports); } return this.dynamicComponentFactoryService.createDynamicComponent( - class CustomDialogComponentInstance extends CustomDialogComponent {}, template, modules).pipe( + class CustomDialogComponentInstance extends CustomDialogComponent {}, template, imports).pipe( mergeMap((componentType) => { const dialogData: CustomDialogContainerData = { controller, diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index 8e1e6f1b3a..80ab61df67 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ComponentFactory, Inject, Injectable, Optional, Type } from '@angular/core'; +import { Inject, Injectable, Optional, Type } from '@angular/core'; import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; import { WidgetService } from '@core/http/widget.service'; import { forkJoin, from, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; @@ -28,7 +28,13 @@ import { } from '@home/models/widget-component.models'; import cssjs from '@core/css/css'; import { UtilsService } from '@core/services/utils.service'; -import { ModulesWithFactories, ResourcesService } from '@core/services/resources.service'; +import { + componentTypeBySelector, + flatModulesWithComponents, + ModulesWithComponents, + modulesWithComponentsToTypes, + ResourcesService +} from '@core/services/resources.service'; import { IWidgetSettingsComponent, Widget, @@ -55,7 +61,6 @@ import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; import { widgetSettingsComponentsMap } from '@home/components/widget/lib/settings/widget-settings.module'; import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module'; import { IBasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; -import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; @Injectable() export class WidgetComponentService { @@ -323,12 +328,12 @@ export class WidgetComponentService { this.cssParser.cssPreviewNamespace = widgetNamespace; this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); const resourceTasks: Observable[] = []; - const modulesTasks: Observable[] = []; + const modulesTasks: Observable[] = []; if (widgetInfo.resources.length > 0) { widgetInfo.resources.filter(r => r.isModule).forEach( (resource) => { modulesTasks.push( - this.resources.loadFactories(resource.url, this.modulesMap).pipe( + this.resources.loadModulesWithComponents(resource.url, this.modulesMap).pipe( catchError((e: Error) => of(e?.message ? e.message : `Failed to load widget resource module: '${resource.url}'`)) ) ); @@ -345,7 +350,7 @@ export class WidgetComponentService { } ); - let modulesObservable: Observable; + let modulesObservable: Observable; if (modulesTasks.length) { modulesObservable = forkJoin(modulesTasks).pipe( map(res => { @@ -353,20 +358,13 @@ export class WidgetComponentService { if (msg) { return msg as string; } else { - const modulesWithFactoriesList = res as ModulesWithFactories[]; - const resModulesWithFactories: ModulesWithFactories = { - modules: modulesWithFactoriesList.map(mf => mf.modules).flat(), - factories: modulesWithFactoriesList.map(mf => mf.factories).flat() - }; - if (modules && modules.length) { - resModulesWithFactories.modules = resModulesWithFactories.modules.concat(modules); - } - return resModulesWithFactories; + const modulesWithComponentsList = res as ModulesWithComponents[]; + return flatModulesWithComponents(modulesWithComponentsList); } }) ); } else { - modulesObservable = modules && modules.length ? of({modules, factories: []}) : of({modules: [], factories: []}); + modulesObservable = of({modules: [], standaloneComponents: []}); } resourceTasks.push( @@ -375,11 +373,15 @@ export class WidgetComponentService { if (typeof resolvedModules === 'string') { return of(resolvedModules); } else { - this.registerWidgetSettingsForms(widgetInfo, resolvedModules.factories); + this.registerWidgetSettingsForms(widgetInfo, resolvedModules); + let imports = modulesWithComponentsToTypes(resolvedModules); + if (modules && modules.length) { + imports = imports.concat(modules); + } return this.dynamicComponentFactoryService.createDynamicComponent( class DynamicWidgetComponentInstance extends DynamicWidgetComponent {}, widgetInfo.templateHtml, - resolvedModules.modules + imports ).pipe( map((componentType) => { widgetInfo.componentType = componentType; @@ -401,7 +403,7 @@ export class WidgetComponentService { errors = msgs.filter(msg => msg && msg.length > 0); } if (errors && errors.length) { - return throwError(errors); + return throwError(() => errors); } else { return of(null); } @@ -409,7 +411,7 @@ export class WidgetComponentService { )); } - private registerWidgetSettingsForms(widgetInfo: WidgetInfo, factories: ComponentFactory[]) { + private registerWidgetSettingsForms(widgetInfo: WidgetInfo, modulesWithComponents: ModulesWithComponents) { const directives: string[] = []; const basicDirectives: string[] = []; if (widgetInfo.settingsDirective && widgetInfo.settingsDirective.length) { @@ -425,17 +427,19 @@ export class WidgetComponentService { basicDirectives.push(widgetInfo.basicModeDirective); } - this.expandSettingComponentMap(widgetSettingsComponentsMap, directives, factories); - this.expandSettingComponentMap(basicWidgetConfigComponentsMap, basicDirectives, factories); + this.expandSettingComponentMap(widgetSettingsComponentsMap, directives, modulesWithComponents); + this.expandSettingComponentMap(basicWidgetConfigComponentsMap, basicDirectives, modulesWithComponents); } private expandSettingComponentMap(settingsComponentsMap: {[key: string]: Type}, - directives: string[], factories: ComponentFactory[]): void { + directives: string[], modulesWithComponents: ModulesWithComponents): void { if (directives.length) { - factories.filter((factory) => directives.includes(factory.selector)) - .forEach((foundFactory) => { - settingsComponentsMap[foundFactory.selector] = foundFactory.componentType; - }); + directives.forEach(selector => { + const compType = componentTypeBySelector(modulesWithComponents, selector); + if (compType) { + settingsComponentsMap[selector] = compType; + } + }); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index 735212c2b3..6be327290d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -17,7 +17,6 @@ import { ChangeDetectorRef, Component, - ComponentFactoryResolver, ComponentRef, forwardRef, Input, @@ -212,7 +211,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe private entityService: EntityService, private dialog: MatDialog, public translate: TranslateService, - private cfr: ComponentFactoryResolver, private fb: UntypedFormBuilder, private cd: ChangeDetectorRef) { super(store); @@ -444,10 +442,9 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe this.basicModeDirectiveError = this.translate.instant('widget-config.settings-component-not-found', {selector: this.modelValue.basicModeDirective}); } else { - const factory = this.cfr.resolveComponentFactory(componentType); this.createBasicModeComponentTimeout = setTimeout(() => { this.createBasicModeComponentTimeout = null; - this.basicModeComponentRef = this.basicModeContainer.createComponent(factory); + this.basicModeComponentRef = this.basicModeContainer.createComponent(componentType); this.basicModeComponent = this.basicModeComponentRef.instance; this.basicModeComponent.isAdd = isAdd; this.basicModeComponent.widgetConfig = this.modelValue; diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index b58e9fe85b..9ff7f24568 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -15,7 +15,6 @@ /// import { - AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, @@ -39,7 +38,8 @@ import { } from '@angular/core'; import { DashboardWidget } from '@home/models/dashboard-component.models'; import { - Widget, WidgetAction, + Widget, + WidgetAction, WidgetActionDescriptor, widgetActionSources, WidgetActionType, @@ -57,7 +57,8 @@ import { WidgetService } from '@core/http/widget.service'; import { UtilsService } from '@core/services/utils.service'; import { forkJoin, Observable, of, ReplaySubject, Subscription, throwError } from 'rxjs'; import { - deepClone, guid, + deepClone, + guid, insertVariable, isDefined, isNotEmptyStr, @@ -87,7 +88,12 @@ import { import { EntityId } from '@shared/models/id/entity-id'; import { ActivatedRoute, Router } from '@angular/router'; import cssjs from '@core/css/css'; -import { ModulesWithFactories, ResourcesService } from '@core/services/resources.service'; +import { + flatModulesWithComponents, + ModulesWithComponents, + modulesWithComponentsToTypes, + ResourcesService +} from '@core/services/resources.service'; import { catchError, map, switchMap } from 'rxjs/operators'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TimeService } from '@core/services/time.service'; @@ -122,7 +128,7 @@ import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) -export class WidgetComponent extends PageComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy { +export class WidgetComponent extends PageComponent implements OnInit, OnChanges, OnDestroy { @Input() widgetTitlePanel: TemplateRef; @@ -326,9 +332,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } } - ngAfterViewInit(): void { - } - ngOnChanges(changes: SimpleChanges): void { for (const propName of Object.keys(changes)) { const change = changes[propName]; @@ -1129,7 +1132,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } const customActionPrettyFunction = new Function('$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams', 'entityLabel', customPrettyFunction); - this.widgetContext.customDialog.setAdditionalModules(descriptor.customModules); + this.widgetContext.customDialog.setAdditionalImports(descriptor.customImports); customActionPrettyFunction($event, this.widgetContext, entityId, entityName, htmlTemplate, additionalParams, entityLabel); } catch (e) { console.error(e); @@ -1459,7 +1462,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI private loadCustomActionResources(actionNamespace: string, customCss: string, customResources: Array, actionDescriptor: WidgetAction): Observable { const resourceTasks: Observable[] = []; - const modulesTasks: Observable[] = []; + const modulesTasks: Observable[] = []; if (isDefined(customCss) && customCss.length > 0) { this.cssParser.cssPreviewNamespace = actionNamespace; @@ -1470,7 +1473,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI customResources.forEach(resource => { if (resource.isModule) { modulesTasks.push( - this.resources.loadFactories(resource.url, this.modulesMap).pipe( + this.resources.loadModulesWithComponents(resource.url, this.modulesMap).pipe( catchError((e: Error) => of(e?.message ? e.message : `Failed to load custom action resource module: '${resource.url}'`)) ) ); @@ -1484,28 +1487,24 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI }); if (modulesTasks.length) { - const modulesObservable: Observable[]> = forkJoin(modulesTasks).pipe( + const importsObservable: Observable[]> = forkJoin(modulesTasks).pipe( map(res => { const msg = res.find(r => typeof r === 'string'); if (msg) { return msg as string; } else { - const modulesWithFactoriesList = res as ModulesWithFactories[]; - const resModulesWithFactories: ModulesWithFactories = { - modules: modulesWithFactoriesList.map(mf => mf.modules).flat(), - factories: modulesWithFactoriesList.map(mf => mf.factories).flat() - }; - return resModulesWithFactories.modules; + const modulesWithComponents = flatModulesWithComponents(res as ModulesWithComponents[]); + return modulesWithComponentsToTypes(modulesWithComponents); } }) ); - resourceTasks.push(modulesObservable.pipe( - map((resolvedModules) => { - if (typeof resolvedModules === 'string') { - return resolvedModules; + resourceTasks.push(importsObservable.pipe( + map((resolvedImports) => { + if (typeof resolvedImports === 'string') { + return resolvedImports; } else { - actionDescriptor.customModules = resolvedModules; + actionDescriptor.customImports = resolvedImports; return null; } }))); diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts index 6109bc40ab..fc73cc9d56 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts @@ -15,14 +15,12 @@ /// import { - AfterViewInit, Component, ComponentRef, EventEmitter, forwardRef, Input, OnDestroy, - OnInit, Output, ViewChild, ViewContainerRef @@ -42,7 +40,6 @@ import { import { Subscription } from 'rxjs'; import { RuleChainService } from '@core/http/rule-chain.service'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { TranslateService } from '@ngx-translate/core'; import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; import { deepClone } from '@core/utils'; import { RuleChainType } from '@shared/models/rule-chain.models'; @@ -57,7 +54,7 @@ import { RuleChainType } from '@shared/models/rule-chain.models'; multi: true }] }) -export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, OnDestroy, AfterViewInit { +export class RuleNodeConfigComponent implements ControlValueAccessor, OnDestroy { @ViewChild('definedConfigContent', {read: ViewContainerRef, static: true}) definedConfigContainer: ViewContainerRef; @@ -121,10 +118,9 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On private configuration: RuleNodeConfiguration; - private propagateChange = (v: any) => { }; + private propagateChange = (_v: any) => { }; - constructor(private translate: TranslateService, - private ruleChainService: RuleChainService, + constructor(private ruleChainService: RuleChainService, private fb: UntypedFormBuilder) { this.ruleNodeConfigFormGroup = this.fb.group({ configuration: [null, Validators.required] @@ -135,10 +131,7 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On this.propagateChange = fn; } - registerOnTouched(fn: any): void { - } - - ngOnInit(): void { + registerOnTouched(_fn: any): void { } ngOnDestroy(): void { @@ -155,9 +148,6 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On } } - ngAfterViewInit(): void { - } - setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { @@ -218,8 +208,8 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On this.changeSubscription = null; } this.definedConfigContainer.clear(); - const factory = this.ruleChainService.getRuleNodeConfigFactory(this.nodeDefinition.configDirective); - this.definedConfigComponentRef = this.definedConfigContainer.createComponent(factory); + const component = this.ruleChainService.getRuleNodeConfigComponent(this.nodeDefinition.configDirective); + this.definedConfigComponentRef = this.definedConfigContainer.createComponent(component); this.definedConfigComponent = this.definedConfigComponentRef.instance; this.definedConfigComponent.ruleNodeId = this.ruleNodeId; this.definedConfigComponent.ruleChainId = this.ruleChainId; diff --git a/ui-ngx/src/app/shared/components/directives/component-outlet.directive.ts b/ui-ngx/src/app/shared/components/directives/component-outlet.directive.ts index 71100527a5..008e5fbc37 100644 --- a/ui-ngx/src/app/shared/components/directives/component-outlet.directive.ts +++ b/ui-ngx/src/app/shared/components/directives/component-outlet.directive.ts @@ -15,13 +15,13 @@ /// import { - ComponentFactory, ComponentRef, + ComponentRef, Directive, EventEmitter, Injector, Input, OnChanges, Output, Renderer2, SimpleChange, SimpleChanges, - TemplateRef, + Type, ViewContainerRef } from '@angular/core'; @@ -36,13 +36,11 @@ export class TbComponentOutletDirective<_T = unknown> implements OnChanges { @Input() tbComponentOutletContext: any | null = null; @Input() tbComponentStyle: { [klass: string]: any } | null = null; @Input() tbComponentInjector: Injector | null = null; - @Input() tbComponentOutlet: ComponentFactory = null; + @Input() tbComponentOutlet: Type = null; @Output() componentChange = new EventEmitter>(); static ngTemplateContextGuard( - // eslint-disable-next-line @typescript-eslint/naming-convention,no-underscore-dangle,id-blacklist,id-match _dir: TbComponentOutletDirective, - // eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match _ctx: any ): _ctx is TbComponentOutletContext { return true; @@ -50,7 +48,7 @@ export class TbComponentOutletDirective<_T = unknown> implements OnChanges { private recreateComponent(): void { this.viewContainer.clear(); - this.componentRef = this.viewContainer.createComponent(this.tbComponentOutlet, 0, this.tbComponentInjector); + this.componentRef = this.viewContainer.createComponent(this.tbComponentOutlet, {index: 0, injector: this.tbComponentInjector}); this.componentChange.next(this.componentRef); if (this.tbComponentOutletContext) { for (const propName of Object.keys(this.tbComponentOutletContext)) { @@ -85,8 +83,8 @@ export class TbComponentOutletDirective<_T = unknown> implements OnChanges { if (tbComponentOutlet.firstChange) { shouldOutletRecreate = true; } else { - const isPreviousOutletTemplate = tbComponentOutlet.previousValue instanceof ComponentFactory; - const isCurrentOutletTemplate = tbComponentOutlet.currentValue instanceof ComponentFactory; + const isPreviousOutletTemplate = tbComponentOutlet.previousValue instanceof Type; + const isCurrentOutletTemplate = tbComponentOutlet.currentValue instanceof Type; shouldOutletRecreate = isPreviousOutletTemplate || isCurrentOutletTemplate; } } diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx index 07683fad82..d9fd64f951 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx @@ -22,18 +22,18 @@ import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json- import { IEditorProps } from 'react-ace/src/types'; import { mergeMap } from 'rxjs/operators'; import { getAce } from '@shared/models/ace/ace.models'; -import { from } from 'rxjs'; +import { from, lastValueFrom } from 'rxjs'; import { Observable } from 'rxjs/internal/Observable'; import { CircularProgress, IconButton } from '@mui/material'; import { MouseEvent } from 'react'; import { Help, HelpOutline } from '@mui/icons-material'; const ReactAce = React.lazy(() => { - return getAce().pipe( + return lastValueFrom(getAce().pipe( mergeMap(() => { return from(import('react-ace')); }) - ).toPromise(); + )); }); interface ThingsboardAceEditorProps extends JsonFormFieldProps { @@ -53,7 +53,7 @@ class ThingsboardAceEditor extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onAppend = this.onAppend.bind(this); this.onDelete = this.onDelete.bind(this); @@ -130,12 +130,10 @@ class ThingsboardArray extends React.Component; diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx index b65a6799bc..4e9687ce3a 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx @@ -21,7 +21,7 @@ import { isDefinedAndNotNull } from '@core/utils'; export default ThingsboardBaseComponent => class

extends React.Component { - constructor(props) { + constructor(props: P) { super(props); this.onChangeValidate = this.onChangeValidate.bind(this); const value = this.defaultValue(); diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-checkbox.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-checkbox.tsx index bd592eabd7..fd3bac8f2c 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-checkbox.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-checkbox.tsx @@ -31,7 +31,7 @@ class ThingsboardCheckbox extends React.Component { + onChange={(e) => { this.props.onChangeValidate(e); }} /> diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx index 48635c8397..bf3a245663 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx @@ -32,7 +32,7 @@ class ThingsboardColor extends React.Component(); - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onBlur = this.onBlur.bind(this); this.onFocus = this.onFocus.bind(this); diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx index bcc0f46111..600ae8c12b 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx @@ -21,7 +21,7 @@ import { beautifyCss } from '@shared/models/beautify.models'; class ThingsboardCss extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onTidyCss = this.onTidyCss.bind(this); } diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-date.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-date.tsx index 868bbd5cb6..3b253cfa8e 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-date.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-date.tsx @@ -26,7 +26,7 @@ interface ThingsboardDateState extends JsonFormFieldState { class ThingsboardDate extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onDatePicked = this.onDatePicked.bind(this); let value: Date | null = null; diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx index 07f9d119cf..f267154182 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx @@ -21,7 +21,7 @@ import { beautifyHtml } from '@shared/models/beautify.models'; class ThingsboardHtml extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onTidyHtml = this.onTidyHtml.bind(this); } diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-icon.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-icon.tsx index fdbd402e95..00366b86e8 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-icon.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-icon.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import { MouseEvent } from 'react'; import ThingsboardBaseComponent from './json-form-base-component'; import reactCSS from 'reactcss'; import TextField from '@mui/material/TextField'; @@ -33,7 +33,7 @@ class ThingsboardIcon extends React.Component(); - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onBlur = this.onBlur.bind(this); this.onFocus = this.onFocus.bind(this); @@ -58,7 +58,7 @@ class ThingsboardIcon extends React.Component { + iconContainer.on('click', (event) => { if (!this.props.form.readonly) { this.onIconClick(event); } @@ -72,14 +72,13 @@ class ThingsboardIcon extends React.Component { this.onValueChanged(color); @@ -87,7 +86,7 @@ class ThingsboardIcon extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onDrop = this.onDrop.bind(this); this.onClear = this.onClear.bind(this); @@ -40,12 +40,12 @@ class ThingsboardImage extends React.Component { - this.onValueChanged(reader.result); + this.onValueChanged(reader.result as string); }; reader.readAsDataURL(acceptedFiles[0]); } - onValueChanged(value) { + onValueChanged(value: string) { this.setState({ imageUrl: value }); @@ -56,7 +56,7 @@ class ThingsboardImage extends React.Component; } else { diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx index 9567062584..74d01137a8 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx @@ -21,7 +21,7 @@ import { beautifyJs } from '@shared/models/beautify.models'; class ThingsboardJavaScript extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onTidyJavascript = this.onTidyJavascript.bind(this); } diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx index a17074eff6..9ab839616b 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx @@ -21,7 +21,7 @@ import { beautifyJs } from '@shared/models/beautify.models'; class ThingsboardJson extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onTidyJson = this.onTidyJson.bind(this); } diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-markdown.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-markdown.tsx index 838bf39151..57fb97ae23 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-markdown.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-markdown.tsx @@ -19,7 +19,7 @@ import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json- class ThingsboardMarkdown extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); } diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-number.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-number.tsx index 90805374a2..4616324de2 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-number.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-number.tsx @@ -17,6 +17,7 @@ import * as React from 'react'; import ThingsboardBaseComponent from './json-form-base-component'; import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; import { TextField } from '@mui/material'; +import { ChangeEvent } from 'react'; interface ThingsboardNumberState extends JsonFormFieldState { focused: boolean; @@ -25,7 +26,7 @@ interface ThingsboardNumberState extends JsonFormFieldState { class ThingsboardNumber extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.preValidationCheck = this.preValidationCheck.bind(this); this.onBlur = this.onBlur.bind(this); @@ -36,7 +37,7 @@ class ThingsboardNumber extends React.Component) { if (this.isNumeric(e.target.value)) { this.setState({ - lastSuccessfulValue: e.target.value + lastSuccessfulValue: Number(e.target.value) }); this.props.onChangeValidate(e); } diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx index f130c2695a..ee21674b94 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx @@ -32,7 +32,7 @@ interface ThingsboardRcSelectState extends JsonFormFieldState { class ThingsboardRcSelect extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onSelect = this.onSelect.bind(this); this.onDeselect = this.onDeselect.bind(this); @@ -96,7 +96,7 @@ class ThingsboardRcSelect extends React.Component { private hasConditions: boolean; private readonly mapper: {[type: string]: any}; - constructor(props) { + constructor(props: JsonFormProps) { super(props); this.mapper = { @@ -121,7 +121,7 @@ class ThingsboardSchemaForm extends React.Component { onIconClick: OnIconClickFn, onToggleFullscreen: onToggleFullscreenFn, onHelpClick: onHelpClickFn, - mapper: {[type: string]: any}): JSX.Element { + mapper: {[type: string]: any}): React.JSX.Element { const type = form.type; const Field = this.mapper[type]; if (!Field) { @@ -143,13 +143,13 @@ class ThingsboardSchemaForm extends React.Component { mapper={mapper} builder={this.builder}/>; } - createSchema(theForm: any[]): JSX.Element { + createSchema(theForm: any[]): React.JSX.Element { const merged = JsonFormUtils.merge(this.props.schema, theForm, this.props.ignore, this.props.option); let mapper = this.mapper; if (this.props.mapper) { mapper = _.merge(this.mapper, this.props.mapper); } - const forms: ReactNode[] = merged.map(function(form, index) { + const forms: ReactNode[] = merged.map(function(form: JsonFormData, index: number) { return this.builder(form, this.props.model, index, this.onChange, this.onColorClick, this.onIconClick, this.onToggleFullscreen, this.onHelpClick, mapper); }.bind(this)); @@ -166,7 +166,7 @@ class ThingsboardSchemaForm extends React.Component { render() { if (this.props.groupInfoes && this.props.groupInfoes.length > 0) { - const content: JSX.Element[] = []; + const content: React.JSX.Element[] = []; for (const info of this.props.groupInfoes) { const forms = this.createSchema(this.props.form[info.formIndex]); const item = ; @@ -182,7 +182,7 @@ export default ThingsboardSchemaForm; interface ThingsboardSchemaGroupProps { info: GroupInfo; - forms: JSX.Element; + forms: React.JSX.Element; } interface ThingsboardSchemaGroupState { @@ -190,14 +190,14 @@ interface ThingsboardSchemaGroupState { } class ThingsboardSchemaGroup extends React.Component { - constructor(props) { + constructor(props: ThingsboardSchemaGroupProps) { super(props); this.state = { showGroup: true }; } - toogleGroup(index) { + toogleGroup() { this.setState({ showGroup: !this.state.showGroup }); diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-select.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-select.tsx index fa296e6ef4..46da881e38 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-select.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-select.tsx @@ -28,7 +28,7 @@ interface ThingsboardSelectState extends JsonFormFieldState { class ThingsboardSelect extends React.Component { - static getDerivedStateFromProps(props) { + static getDerivedStateFromProps(props: JsonFormFieldProps) { if (props.model && props.form.key) { return { currentValue: ThingsboardSelect.getModelKey(props.model, props.form.key) @@ -37,7 +37,7 @@ class ThingsboardSelect extends React.Component (cur[nxt] || {}), model); if (res && isObject(res)) { @@ -50,7 +50,7 @@ class ThingsboardSelect extends React.Component { - constructor(props) { + constructor(props: JsonFormFieldProps) { super(props); this.onBlur = this.onBlur.bind(this); this.onFocus = this.onFocus.bind(this); diff --git a/ui-ngx/src/app/shared/components/popover.component.ts b/ui-ngx/src/app/shared/components/popover.component.ts index 93a126dbd3..f686f5d7a5 100644 --- a/ui-ngx/src/app/shared/components/popover.component.ts +++ b/ui-ngx/src/app/shared/components/popover.component.ts @@ -19,7 +19,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ComponentFactory, ComponentRef, Directive, ElementRef, @@ -34,6 +33,7 @@ import { Renderer2, SimpleChanges, TemplateRef, + Type, ViewChild, ViewContainerRef, ViewEncapsulation @@ -43,7 +43,8 @@ import { CdkConnectedOverlay, CdkOverlayOrigin, ConnectedOverlayPositionChange, - ConnectionPositionPair, NoopScrollStrategy + ConnectionPositionPair, + NoopScrollStrategy } from '@angular/cdk/overlay'; import { Subject, Subscription } from 'rxjs'; import { @@ -344,8 +345,8 @@ export class TbPopoverDirective implements OnChanges, OnDestroy, AfterViewInit { {{ tbContent }} - implements OnDestroy, OnInit { @ViewChild('popover', { static: false }) popover!: ElementRef; tbContent: string | TemplateRef | null = null; - tbComponentFactory: ComponentFactory | null = null; + tbComponent: Type | null = null; tbComponentRef: ComponentRef | null = null; tbComponentContext: any; tbComponentInjector: Injector | null = null; @@ -675,7 +676,7 @@ export class TbPopoverComponent implements OnDestroy, OnInit { } private isEmpty(): boolean { - return (this.tbComponentFactory instanceof ComponentFactory || this.tbContent instanceof TemplateRef) + return (this.tbComponent instanceof Type || this.tbContent instanceof TemplateRef) ? false : !isNotEmptyStr(this.tbContent); } } diff --git a/ui-ngx/src/app/shared/components/popover.service.ts b/ui-ngx/src/app/shared/components/popover.service.ts index 4b7e67a575..9da5944882 100644 --- a/ui-ngx/src/app/shared/components/popover.service.ts +++ b/ui-ngx/src/app/shared/components/popover.service.ts @@ -15,8 +15,6 @@ /// import { - ComponentFactory, - ComponentFactoryResolver, ComponentRef, ElementRef, Inject, @@ -26,12 +24,7 @@ import { Type, ViewContainerRef } from '@angular/core'; -import { - PopoverPlacement, - PopoverPreferredPlacement, - PopoverWithTrigger, - StrictPopoverPlacement -} from '@shared/components/popover.models'; +import { PopoverPreferredPlacement, PopoverWithTrigger } from '@shared/components/popover.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { ComponentType } from '@angular/cdk/portal'; import { HELP_MARKDOWN_COMPONENT_TOKEN } from '@shared/components/tokens'; @@ -42,10 +35,7 @@ export class TbPopoverService { private popoverWithTriggers: PopoverWithTrigger[] = []; - componentFactory: ComponentFactory = this.resolver.resolveComponentFactory(TbPopoverComponent); - - constructor(private resolver: ComponentFactoryResolver, - @Inject(HELP_MARKDOWN_COMPONENT_TOKEN) private helpMarkdownComponent: ComponentType) { + constructor(@Inject(HELP_MARKDOWN_COMPONENT_TOKEN) private helpMarkdownComponent: ComponentType) { } hasPopover(trigger: Element): boolean { @@ -64,7 +54,7 @@ export class TbPopoverService { } createPopoverRef(hostView: ViewContainerRef): ComponentRef { - return hostView.createComponent(this.componentFactory); + return hostView.createComponent(TbPopoverComponent); } displayPopover(trigger: Element, renderer: Renderer2, hostView: ViewContainerRef, @@ -96,7 +86,7 @@ export class TbPopoverService { const originElementRef = new ElementRef(trigger); component.setOverlayOrigin(new CdkOverlayOrigin(originElementRef)); component.tbPlacement = preferredPlacement; - component.tbComponentFactory = this.resolver.resolveComponentFactory(componentType); + component.tbComponent = componentType; component.tbComponentInjector = injector; component.tbComponentContext = context; component.tbOverlayStyle = overlayStyle; @@ -134,7 +124,7 @@ export class TbPopoverService { const injector = Injector.create({ parent: hostView.injector, providers: [] }); - const componentRef = hostView.createComponent(this.componentFactory); + const componentRef = hostView.createComponent(TbPopoverComponent); const component = componentRef.instance; this.popoverWithTriggers.push({ trigger, @@ -149,7 +139,7 @@ export class TbPopoverService { component.tbOverlayStyle = {...overlayStyle, opacity: '0' }; component.setOverlayOrigin(new CdkOverlayOrigin(originElementRef)); component.tbPlacement = preferredPlacement; - component.tbComponentFactory = this.resolver.resolveComponentFactory(this.helpMarkdownComponent); + component.tbComponent = this.helpMarkdownComponent; component.tbComponentInjector = injector; component.tbComponentContext = { helpId, diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index 582be5a577..1a8f07c3ea 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -16,7 +16,7 @@ import { AfterViewInit, ChangeDetectorRef, - Component, ComponentFactoryResolver, ComponentRef, + Component, ComponentRef, Directive, ElementRef, HostBinding, Inject, @@ -57,7 +57,6 @@ export class ToastDirective implements AfterViewInit, OnDestroy { constructor(private elementRef: ElementRef, private viewContainerRef: ViewContainerRef, private notificationService: ToastNotificationService, - private componentFactoryResolver: ComponentFactoryResolver, private snackBar: MatSnackBar, private ngZone: NgZone, private breakpointObserver: BreakpointObserver, @@ -131,7 +130,6 @@ export class ToastDirective implements AfterViewInit, OnDestroy { panelClass.push('bottom'); } - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(TbSnackBarComponent); const data: ToastPanelData = { notification: notificationMessage, panelClass, @@ -144,7 +142,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy { {provide: MAT_SNACK_BAR_DATA, useValue: data} ]; const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); - this.toastComponentRef = this.viewContainerRef.createComponent(componentFactory, 0, injector); + this.toastComponentRef = this.viewContainerRef.createComponent(TbSnackBarComponent, {index: 0, injector}); this.cd.detectChanges(); if (notificationMessage.duration && notificationMessage.duration > 0) { diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 429bb79e35..65c09a52e1 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -659,7 +659,7 @@ export interface CustomActionDescriptor { customResources?: Array; customHtml?: string; customCss?: string; - customModules?: Type[]; + customImports?: Type[]; } export interface WidgetAction extends CustomActionDescriptor {