Browse Source

UI: Remove deprecated ComponentFactory usage. Improve typings in tsx files.

pull/11819/head
Igor Kulikov 2 years ago
parent
commit
c19924065c
  1. 3
      ui-ngx/.eslintrc.json
  2. 23
      ui-ngx/src/app/core/http/rule-chain.service.ts
  3. 6
      ui-ngx/src/app/core/services/dynamic-component-factory.service.ts
  4. 209
      ui-ngx/src/app/core/services/resources.service.ts
  5. 6
      ui-ngx/src/app/modules/common/modules-map.ts
  6. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts
  7. 9
      ui-ngx/src/app/modules/home/components/dashboard-page/states/states-controller.service.ts
  8. 6
      ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts
  9. 5
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  10. 5
      ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts
  11. 10
      ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts
  12. 19
      ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts
  13. 14
      ui-ngx/src/app/modules/home/components/profile/asset-profile-dialog.component.ts
  14. 14
      ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.ts
  15. 14
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.ts
  16. 16
      ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts
  17. 14
      ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts
  18. 58
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  19. 5
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  20. 45
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  21. 22
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts
  22. 14
      ui-ngx/src/app/shared/components/directives/component-outlet.directive.ts
  23. 8
      ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx
  24. 6
      ui-ngx/src/app/shared/components/json-form/react/json-form-array.tsx
  25. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx
  26. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-checkbox.tsx
  27. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx
  28. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx
  29. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-date.tsx
  30. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx
  31. 11
      ui-ngx/src/app/shared/components/json-form/react/json-form-icon.tsx
  32. 12
      ui-ngx/src/app/shared/components/json-form/react/json-form-image.tsx
  33. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx
  34. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx
  35. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-markdown.tsx
  36. 9
      ui-ngx/src/app/shared/components/json-form/react/json-form-number.tsx
  37. 6
      ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx
  38. 16
      ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx
  39. 6
      ui-ngx/src/app/shared/components/json-form/react/json-form-select.tsx
  40. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-text.tsx
  41. 13
      ui-ngx/src/app/shared/components/popover.component.ts
  42. 22
      ui-ngx/src/app/shared/components/popover.service.ts
  43. 6
      ui-ngx/src/app/shared/components/toast.directive.ts
  44. 2
      ui-ngx/src/app/shared/models/widget.models.ts

3
ui-ngx/.eslintrc.json

@ -6,7 +6,8 @@
"overrides": [
{
"files": [
"*.ts"
"*.ts",
"*.tsx"
],
"parserOptions": {
"project": [

23
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<RuleChainType, Array<RuleNodeComponentDescriptor>> =
new Map<RuleChainType, Array<RuleNodeComponentDescriptor>>();
private ruleNodeConfigFactories: {[directive: string]: ComponentFactory<IRuleNodeConfigurationComponent>} = {};
private ruleNodeConfigComponents: {[directive: string]: Type<IRuleNodeConfigurationComponent>} = {};
constructor(
private http: HttpClient,
@ -126,8 +128,8 @@ export class RuleChainService {
}
}
public getRuleNodeConfigFactory(directive: string): ComponentFactory<IRuleNodeConfigurationComponent> {
return this.ruleNodeConfigFactories[directive];
public getRuleNodeConfigComponent(directive: string): Type<IRuleNodeConfigurationComponent> {
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',

6
ui-ngx/src/app/core/services/dynamic-component-factory.service.ts

@ -31,14 +31,14 @@ export class DynamicComponentFactoryService {
public createDynamicComponent<T>(
componentType: Type<T>,
template: string,
modules?: Type<any>[],
imports?: Type<any>[],
preserveWhitespaces?: boolean,
styles?: string[]): Observable<Type<T>> {
return from(import('@angular/compiler')).pipe(
mergeMap(() => {
let componentImports: Type<any>[] = [CommonModule];
if (modules) {
componentImports = [...componentImports, ...modules];
if (imports) {
componentImports = [...componentImports, ...imports];
}
const comp = this.createAndCompileDynamicComponent(componentType, template, componentImports, preserveWhitespaces, styles);
return of(comp.type);

209
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<any>;
components: ɵComponentDef<any>[];
}
export interface ModulesWithComponents {
modules: ModuleInfo[];
standaloneComponents: ɵComponentDef<any>[];
}
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<any>[] =>
[...modulesWithComponents.modules.map(m => m.module.type),
...modulesWithComponents.standaloneComponents.map(c => c.type)];
export interface ModulesWithFactories {
modules: Type<any>[];
factories: ComponentFactory<any>[];
export const componentTypeBySelector = (modulesWithComponents: ModulesWithComponents, selector: string): Type<any> | 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<any> } = {};
private loadedResources: { [url: string]: ReplaySubject<void> } = {};
private loadedModulesAndFactories: { [url: string]: ReplaySubject<ModulesWithFactories> } = {};
private loadedModulesWithComponents: { [url: string]: ReplaySubject<ModulesWithComponents> } = {};
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<AppState>,
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<T>(url: string, postProcess?: (data: T) => T): Observable<T> {
@ -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<ModulesWithFactories> {
public loadModulesWithComponents(resourceId: string | TbResourceId, modulesMap: IModulesMap): Observable<ModulesWithComponents> {
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<ModulesWithFactories>();
this.loadedModulesAndFactories[url] = subject;
const subject = new ReplaySubject<ModulesWithComponents>();
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<ModuleWithComponentFactories<any>>[] = [];
for (const m of modules) {
tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m));
}
forkJoin(tasks).subscribe({
next: (compiled) => {
try {
const componentFactories: ComponentFactory<any>[] = [];
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<any>[] = []): Type<any>[] {
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<any> = new Set<any>()): ModulesWithComponents {
if (module && ['object', 'function'].includes(typeof module)) {
if (ɵNG_MOD_DEF in module) {
const moduleDef: ɵNgModuleDef<any> = 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<any>[];
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<any> = 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<any> = 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<any> {
const subject = new ReplaySubject<void>();
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 = {};
}
}

6
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 };
}

3
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();

9
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<IStateControllerComponent>;
component: Type<IStateControllerComponent>;
}
@Injectable()
@ -29,13 +29,12 @@ export class StatesControllerService {
statesControllerStates: {[stateControllerInstanceId: string]: any} = {};
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
constructor() {
}
public registerStatesController(stateControllerId: string, stateControllerComponent: Type<IStateControllerComponent>): void {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(stateControllerComponent);
this.statesControllers[stateControllerId] = {
factory: componentFactory
component: stateControllerComponent
};
}

6
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<BaseData<HasId>>,
public dialogRef: MatDialogRef<AddEntityDialogComponent, BaseData<HasId>>,
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;

5
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;
}

5
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<AppState>) {
super(store, injector, cd, componentFactoryResolver);
super(store, injector, cd);
this.entitiesTableConfig = this.route.snapshot.data.entitiesTableConfig;
this.backNavigationCommands = this.route.snapshot.data.backNavigationCommands;
}

10
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<AppState>,
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;

19
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<AddDeviceProfileDialogComponent, DeviceProfile> implements AfterViewInit {
DialogComponent<AddDeviceProfileDialogComponent, DeviceProfile> {
@ViewChild('addDeviceProfileStepper', {static: true}) addDeviceProfileStepper: MatStepper;
stepperOrientation: Observable<StepperOrientation>;
@ -105,10 +96,7 @@ export class AddDeviceProfileDialogComponent extends
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData,
public dialogRef: MatDialogRef<AddDeviceProfileDialogComponent, DeviceProfile>,
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);
}

14
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<AssetProfileDialogComponent, AssetProfile>,
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
private assetProfileService: AssetProfileService) {
super(store, router, dialogRef);

14
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<DeviceProfileDialogComponent, DeviceProfile>,
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
private deviceProfileService: DeviceProfileService) {
super(store, router, dialogRef);

14
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<TenantProfileDialogComponent, TenantProfile>,
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
private tenantProfileService: TenantProfileService) {
super(store, router, dialogRef);

16
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;

14
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<Type<any>>;
private customImports: Array<Type<any>>;
constructor(
private dynamicComponentFactoryService: DynamicComponentFactoryService,
@ -47,19 +47,19 @@ export class CustomDialogService {
) {
}
setAdditionalModules(modules: Array<Type<any>>) {
this.customModules = modules;
setAdditionalImports(imports: Array<Type<any>>) {
this.customImports = imports;
}
customDialog(template: string, controller: (instance: CustomDialogComponent) => void, data?: any,
config?: MatDialogConfig): Observable<any> {
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,

58
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<string>[] = [];
const modulesTasks: Observable<ModulesWithFactories | string>[] = [];
const modulesTasks: Observable<ModulesWithComponents | string>[] = [];
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<string | ModulesWithFactories>;
let modulesObservable: Observable<string | ModulesWithComponents>;
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<any>[]) {
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<IWidgetSettingsComponent | IBasicWidgetConfigComponent>},
directives: string[], factories: ComponentFactory<any>[]): 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;
}
});
}
}

5
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;

45
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<any>;
@ -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<WidgetResource>,
actionDescriptor: WidgetAction): Observable<any> {
const resourceTasks: Observable<string>[] = [];
const modulesTasks: Observable<ModulesWithFactories | string>[] = [];
const modulesTasks: Observable<ModulesWithComponents | string>[] = [];
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<string | Type<any>[]> = forkJoin(modulesTasks).pipe(
const importsObservable: Observable<string | Type<any>[]> = 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;
}
})));

22
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;

14
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<any> = null;
@Input() tbComponentOutlet: Type<any> = null;
@Output() componentChange = new EventEmitter<ComponentRef<any>>();
static ngTemplateContextGuard<T>(
// eslint-disable-next-line @typescript-eslint/naming-convention,no-underscore-dangle,id-blacklist,id-match
_dir: TbComponentOutletDirective<T>,
// 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;
}
}

8
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<ThingsboardAceEditorProps, Th
private aceEditor: IEditorProps;
constructor(props) {
constructor(props: ThingsboardAceEditorProps) {
super(props);
this.onValueChanged = this.onValueChanged.bind(this);
this.onBlur = this.onBlur.bind(this);

6
ui-ngx/src/app/shared/components/json-form/react/json-form-array.tsx

@ -35,7 +35,7 @@ interface ThingsboardArrayState extends JsonFormFieldState {
class ThingsboardArray extends React.Component<JsonFormFieldProps, ThingsboardArrayState> {
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<JsonFormFieldProps, ThingsboardAr
render() {
const arrays = [];
const fields = [];
const model = this.state.model;
const keys = this.state.keys;
const items = this.props.form.items;
for (let i = 0; i < model.length; i++ ) {
let removeButton: JSX.Element = null;
let removeButton: React.JSX.Element = null;
if (!this.props.form.readonly) {
const boundOnDelete = this.onDelete.bind(this, i);
removeButton = <Tooltip title='Remove'><IconButton onClick={boundOnDelete}><Clear/></IconButton></Tooltip>;

2
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<P extends JsonFormFieldProps>
extends React.Component<P, JsonFormFieldState> {
constructor(props) {
constructor(props: P) {
super(props);
this.onChangeValidate = this.onChangeValidate.bind(this);
const value = this.defaultValue();

2
ui-ngx/src/app/shared/components/json-form/react/json-form-checkbox.tsx

@ -31,7 +31,7 @@ class ThingsboardCheckbox extends React.Component<JsonFormFieldProps, JsonFormFi
value={this.props.form.key.slice(-1)[0]}
checked={this.props.value || false}
disabled={this.props.form.readonly}
onChange={(e, checked) => {
onChange={(e) => {
this.props.onChangeValidate(e);
}}
/>

2
ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx

@ -32,7 +32,7 @@ class ThingsboardColor extends React.Component<JsonFormFieldProps, ThingsboardCo
containerRef = React.createRef<HTMLDivElement>();
constructor(props) {
constructor(props: JsonFormFieldProps) {
super(props);
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);

2
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<JsonFormFieldProps, JsonFormFieldState> {
constructor(props) {
constructor(props: JsonFormFieldProps) {
super(props);
this.onTidyCss = this.onTidyCss.bind(this);
}

2
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<JsonFormFieldProps, ThingsboardDateState> {
constructor(props) {
constructor(props: JsonFormFieldProps) {
super(props);
this.onDatePicked = this.onDatePicked.bind(this);
let value: Date | null = null;

2
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<JsonFormFieldProps, JsonFormFieldState> {
constructor(props) {
constructor(props: JsonFormFieldProps) {
super(props);
this.onTidyHtml = this.onTidyHtml.bind(this);
}

11
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<JsonFormFieldProps, ThingsboardIco
containerRef = React.createRef<HTMLDivElement>();
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<JsonFormFieldProps, ThingsboardIco
componentDidMount() {
const node = this.containerRef.current;
const iconContainer = $(node).children('#icon-container');
iconContainer.click((event) => {
iconContainer.on('click', (event) => {
if (!this.props.form.readonly) {
this.onIconClick(event);
}
@ -72,14 +72,13 @@ class ThingsboardIcon extends React.Component<JsonFormFieldProps, ThingsboardIco
}
onValueChanged(value: string | null) {
const icon = value;
this.setState({
icon: value
});
this.props.onChange(this.props.form.key, value);
}
onIconClick(event) {
onIconClick(_event) {
this.props.onIconClick(this.props.form.key, this.state.icon,
(color) => {
this.onValueChanged(color);
@ -87,7 +86,7 @@ class ThingsboardIcon extends React.Component<JsonFormFieldProps, ThingsboardIco
);
}
onClear(event) {
onClear(event: MouseEvent) {
if (event) {
event.stopPropagation();
}

12
ui-ngx/src/app/shared/components/json-form/react/json-form-image.tsx

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import React, { MouseEvent } from 'react';
import Dropzone from 'react-dropzone';
import ThingsboardBaseComponent from './json-form-base-component';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
@ -27,7 +27,7 @@ interface ThingsboardImageState extends JsonFormFieldState {
class ThingsboardImage extends React.Component<JsonFormFieldProps, ThingsboardImageState> {
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<JsonFormFieldProps, ThingsboardIm
onDrop(acceptedFiles: File[]) {
const reader = new FileReader();
reader.onload = () => {
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<JsonFormFieldProps, ThingsboardIm
});
}
onClear(event) {
onClear(event: MouseEvent) {
if (event) {
event.stopPropagation();
}
@ -73,7 +73,7 @@ class ThingsboardImage extends React.Component<JsonFormFieldProps, ThingsboardIm
labelClass += ' tb-readonly';
}
let previewComponent;
let previewComponent: React.JSX.Element;
if (this.state.imageUrl) {
previewComponent = <img className='tb-image-preview' src={this.state.imageUrl} />;
} else {

2
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<JsonFormFieldProps, JsonFormFieldState> {
constructor(props) {
constructor(props: JsonFormFieldProps) {
super(props);
this.onTidyJavascript = this.onTidyJavascript.bind(this);
}

2
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<JsonFormFieldProps, JsonFormFieldState> {
constructor(props) {
constructor(props: JsonFormFieldProps) {
super(props);
this.onTidyJson = this.onTidyJson.bind(this);
}

2
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<JsonFormFieldProps, JsonFormFieldState> {
constructor(props) {
constructor(props: JsonFormFieldProps) {
super(props);
}

9
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<JsonFormFieldProps, ThingsboardNumberState> {
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<JsonFormFieldProps, ThingsboardN
};
}
isNumeric(n) {
isNumeric(n: any) {
return n === null || n === '' || !isNaN(n) && isFinite(n);
}
@ -48,10 +49,10 @@ class ThingsboardNumber extends React.Component<JsonFormFieldProps, ThingsboardN
this.setState({focused: true});
}
preValidationCheck(e) {
preValidationCheck(e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) {
if (this.isNumeric(e.target.value)) {
this.setState({
lastSuccessfulValue: e.target.value
lastSuccessfulValue: Number(e.target.value)
});
this.props.onChangeValidate(e);
}

6
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<JsonFormFieldProps, ThingsboardRcSelectState> {
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<JsonFormFieldProps, Thingsboar
return index;
}
onSelect(value: KeyLabelItem, option) {
onSelect(value: KeyLabelItem) {
if (this.props.form.schema.type === 'array') {
const v = this.state.currentValue as KeyLabelItem[];
v.push(this.keyToCurrentValue(value.key, false) as KeyLabelItem);
@ -110,7 +110,7 @@ class ThingsboardRcSelect extends React.Component<JsonFormFieldProps, Thingsboar
}
}
onDeselect(value: KeyLabelItem, option) {
onDeselect(value: KeyLabelItem) {
if (this.props.form.schema.type === 'array') {
const v = this.state.currentValue as KeyLabelItem[];
const index = this.keyIndex(v, value.key);

16
ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx

@ -53,7 +53,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
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<JsonFormProps, any> {
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<JsonFormProps, any> {
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<JsonFormProps, any> {
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 = <ThingsboardSchemaGroup key={content.length} forms={forms} info={info}></ThingsboardSchemaGroup>;
@ -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<ThingsboardSchemaGroupProps, ThingsboardSchemaGroupState> {
constructor(props) {
constructor(props: ThingsboardSchemaGroupProps) {
super(props);
this.state = {
showGroup: true
};
}
toogleGroup(index) {
toogleGroup() {
this.setState({
showGroup: !this.state.showGroup
});

6
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<JsonFormFieldProps, ThingsboardSelectState> {
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<JsonFormFieldProps, ThingsboardS
}
}
static getModelKey(model, key) {
static getModelKey(model: any, key: (string | number)[]) {
if (Array.isArray(key)) {
const res = key.reduce((cur, nxt) => (cur[nxt] || {}), model);
if (res && isObject(res)) {
@ -50,7 +50,7 @@ class ThingsboardSelect extends React.Component<JsonFormFieldProps, ThingsboardS
}
}
constructor(props) {
constructor(props: JsonFormFieldProps) {
super(props);
this.onSelected = this.onSelected.bind(this);
const possibleValue = ThingsboardSelect.getModelKey(this.props.model, this.props.form.key);

2
ui-ngx/src/app/shared/components/json-form/react/json-form-text.tsx

@ -24,7 +24,7 @@ interface ThingsboardTextState extends JsonFormFieldState {
class ThingsboardText extends React.Component<JsonFormFieldProps, ThingsboardTextState> {
constructor(props) {
constructor(props: JsonFormFieldProps) {
super(props);
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);

13
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 }}
</ng-container>
</ng-container>
<ng-container *ngIf="tbComponentFactory"
[tbComponentOutlet]="tbComponentFactory"
<ng-container *ngIf="tbComponent"
[tbComponentOutlet]="tbComponent"
[tbComponentInjector]="tbComponentInjector"
[tbComponentOutletContext]="tbComponentContext"
(componentChange)="onComponentChange($event)"
@ -367,7 +368,7 @@ export class TbPopoverComponent<T = any> implements OnDestroy, OnInit {
@ViewChild('popover', { static: false }) popover!: ElementRef<HTMLElement>;
tbContent: string | TemplateRef<void> | null = null;
tbComponentFactory: ComponentFactory<T> | null = null;
tbComponent: Type<T> | null = null;
tbComponentRef: ComponentRef<T> | null = null;
tbComponentContext: any;
tbComponentInjector: Injector | null = null;
@ -675,7 +676,7 @@ export class TbPopoverComponent<T = any> 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);
}
}

22
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<TbPopoverComponent> = this.resolver.resolveComponentFactory(TbPopoverComponent);
constructor(private resolver: ComponentFactoryResolver,
@Inject(HELP_MARKDOWN_COMPONENT_TOKEN) private helpMarkdownComponent: ComponentType<any>) {
constructor(@Inject(HELP_MARKDOWN_COMPONENT_TOKEN) private helpMarkdownComponent: ComponentType<any>) {
}
hasPopover(trigger: Element): boolean {
@ -64,7 +54,7 @@ export class TbPopoverService {
}
createPopoverRef(hostView: ViewContainerRef): ComponentRef<TbPopoverComponent> {
return hostView.createComponent(this.componentFactory);
return hostView.createComponent(TbPopoverComponent);
}
displayPopover<T>(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,

6
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) {

2
ui-ngx/src/app/shared/models/widget.models.ts

@ -659,7 +659,7 @@ export interface CustomActionDescriptor {
customResources?: Array<WidgetResource>;
customHtml?: string;
customCss?: string;
customModules?: Type<any>[];
customImports?: Type<any>[];
}
export interface WidgetAction extends CustomActionDescriptor {

Loading…
Cancel
Save