ThingsBoard Widget Action 实战:用 customDialog 与服务注入实现“克隆设备“对话框

发布时间:2026/10/3 8:23:04
ThingsBoard Widget Action 实战:用 customDialog 与服务注入实现“克隆设备“对话框 物联网后端数据可视化消息队列【免费下载链接】thingsboardAll-in-one IoT Platform - Device management, data collection, processing and visualization.项目地址https://gitcode.com/GitHub_Trending/th/thingsboard点击查看免费下载本篇指南聚焦 ThingsBoard 前端ui-ngx中自定义 Widget Action 的高级用法以官方帮助文档中克隆设备Clone Device示例为核心完整讲解如何通过widgetContext的$injector与servicesMap注入deviceService、attributeService等 Angular 服务配合customDialog动态渲染自定义对话框并使用 RxJSmergeMap链实现读取原设备 → 创建新设备 → 拷贝服务端属性 → 刷新数据源的完整闭环。读完本篇你将掌握在 ThingsBoard 表格/卡片等组件动作中编写可复用、可交互的自定义 JS Action 的完整方法论并理解其底层运行原理。一、应用场景从表格行按钮到设备克隆在 ThingsBoard 的仪表板中表格Table、卡片Card、实体列表等 Widget 的Actions配置允许你在每一行数据上挂接自定义交互。本示例演示的场景是点击某一台设备的行内按钮弹出一个对话框输入新设备名称即可克隆该设备并复制其服务端属性。这份示例代码位于仓库 custom_pretty_clone_device_js.md同目录下还配套了对话框 HTML 模板文档 custom_pretty_clone_device_html.md。官方还提供了同风格的其他示例创建实体对话框、编辑实体、创建用户、编辑图片等可参考ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/目录下的其他*_js.md/*_html.md文件。二、核心 APIWidgetContext 与依赖注入示例代码的第一段就是整个脚本的运行基础const $injector widgetContext.$scope.$injector; const customDialog $injector.get(widgetContext.servicesMap.get(customDialog)); const attributeService $injector.get(widgetContext.servicesMap.get(attributeService)); const deviceService $injector.get(widgetContext.servicesMap.get(deviceService)); const rxjs widgetContext.rxjs;逐行说明表达式含义widgetContext.$scope.$injector当前动态组件作用域的 AngularInjector是获取服务的唯一入口。widgetContext.$scope即动态 Widget 组件的 scope对应源码中的IDynamicWidgetComponentwidgetContext.servicesMap.get(customDialog)从服务映射表中取出服务类TokenservicesMap是Mapstring, Typeany把字符串名映射到服务类型$injector.get(...)通过 Angular 依赖注入真正拿到服务实例widgetContext.rxjs由源码 widget-component.models.ts 定义的rxjs对象它展开合并了rxjs与rxjs/operators的全部导出因此mergeMap、of等操作符可直接以rxjs.mergeMap(...)形式调用无需再单独 import从源码WidgetContext类定义widget-component.models.ts可以看到servicesMap字段类型为Mapstring, Typeany$injector类型为Injector$scope为动态组件 scope。官方自带的完整示例custom-sample-js.raw见 custom-sample-js.raw中也大量采用$injector.get(widgetContext.servicesMap.get(xxxService))这一标准写法可见这是 ThingsBoard 自定义 Action 获取后端服务的事实标准模式。本示例注入了三个服务customDialog动态对话框服务源码位于 custom-dialog.service.ts其核心方法签名如下customDialog(template: string, controller: (instance: CustomDialogComponent) void, data?: any, config?: MatDialogConfig): Observableany它接收一段HTML 模板字符串和一个控制器函数通过dynamicComponentFactoryService.createDynamicComponent在运行时编译模板、打开MatDialog对话框并在关闭后销毁动态组件。deviceService设备 CRUD 服务源码位于ui-ngx/src/app/core/http/device.service.ts提供getDevice、saveDevice等方法。attributeService属性服务源码位于ui-ngx/src/app/core/http/attribute.service.ts提供getEntityAttributes、saveEntityAttributes等方法。三、打开克隆对话框openCloneDeviceDialog(); function openCloneDeviceDialog() { customDialog.customDialog(htmlTemplate, CloneDeviceDialogController).subscribe(); }htmlTemplate是你在 Action 的HTML 模板编辑区里写的字符串即姐妹文档 custom_pretty_clone_device_html.md 提供的那份表单模板。customDialog()返回一个Observablesubscribe()触发对话框打开对话框关闭后该流完成。提示htmlTemplate变量名需与 HTML 模板编辑区中的命名一致ThingsBoard 会把两个编辑区的内容在同一作用域内求值。官方示例在 HTML 侧同样命名为htmlTemplate。四、对话框控制器表单初始化与按钮逻辑控制器函数在对话框组件实例上挂载逻辑function CloneDeviceDialogController(instance) { let vm instance; vm.deviceName entityName; vm.cloneDeviceFormGroup vm.fb.group({ cloneName: [, [vm.validators.required]] }); vm.save function() { /* ... 见下节 ... */ }; vm.cancel function() { vm.dialogRef.close(null); }; }要点instancevm是CustomDialogComponent实例自带fbFormBuilder、validatorsAngular Validators、dialogRefMatDialogRef等可选项直接挂载属性与方法即可被模板引用。entityName是当前行实体的名称由 Widget Action 上下文自动注入可直接作为变量使用无需声明同理entityId是当前行实体 ID。它们与数据行数据entityId/entityName一一对应源码中WidgetContext相关定义可参见 widget-component.models.ts 中实体参数的注入逻辑。表单校验规则cloneName必填vm.validators.required与 HTML 模板中的required输入框及hasError(required)错误提示配合使用。配套 HTML 模板节选自 custom_pretty_clone_device_html.md通过[formGroup]cloneDeviceFormGroup绑定表单(ngSubmit)save()提交保存Save 按钮在cloneDeviceFormGroup.invalid或表单未变更!dirty时禁用同时用isLoading$ | async控制进度条显示。五、克隆主流程RxJS mergeMap 链保存逻辑是整个示例的核心vm.save function() { deviceService.getDevice(entityId.id).pipe( rxjs.mergeMap((origDevice) { let cloneDevice { name: vm.cloneDeviceFormGroup.get(cloneName).value, type: origDevice.type }; return deviceService.saveDevice(cloneDevice).pipe( rxjs.mergeMap((newDevice) { return attributeService.getEntityAttributes(origDevice.id, SERVER_SCOPE).pipe( rxjs.mergeMap((origAttributes) { return attributeService.saveEntityAttributes(newDevice.id, SERVER_SCOPE, origAttributes); }) ); }) ); }) ).subscribe(() { widgetContext.updateAliases(); vm.dialogRef.close(null); }); };该链的执行顺序与数据流deviceService.getDevice(entityId.id)—— 根据当前行实体的entityId.id读取原始设备完整信息基于原始设备构造克隆体{ name: 新名称, type: 原类型 }仅复制名称与类型其余如配置文件、证书等不在本示例范围内deviceService.saveDevice(cloneDevice)—— 保存新设备返回新设备对象attributeService.getEntityAttributes(origDevice.id, SERVER_SCOPE)—— 读取原设备服务端作用域SERVER_SCOPE的全部属性attributeService.saveEntityAttributes(newDevice.id, SERVER_SCOPE, origAttributes)—— 把属性原样写入新设备链尾subscribe(() {...})中调用widgetContext.updateAliases()刷新 Widget 数据源让新设备立刻出现在当前表格/列表中并关闭对话框。采用嵌套mergeMap而非forkJoin的原因很直观第 4、5 步强依赖第 3 步返回的newDevice整个流程是严格的串行依赖嵌套mergeMap是最贴合语义的表达方式。六、属性拷贝范围与作用域说明SERVER_SCOPE是 ThingsBoard 三种属性作用域之一另两种为CLIENT_SCOPE与SHARED_SCOPE。本示例只拷贝服务端属性。getEntityAttributes(origDevice.id, SERVER_SCOPE)返回该设备全部服务端属性数组saveEntityAttributes(newDevice.id, SERVER_SCOPE, origAttributes)的第三参数直接接收该数组整体写入attributeService底层会将其拆分为逐个属性保存请求。若需同时拷贝其他作用域可按同样模式在 mergeMap 链中继续追加若需跳过属性为空等边界情况可在saveEntityAttributes前加rxjs.filter判断。七、实战配置与注意事项配置入口在 ThingsBoard 仪表板编辑模式下打开目标 Widget 的Actions配置选择行内动作如Row click或自定义按钮将 Action Type 设置为基于 JavaScript 的自定义动作然后在HTML编辑区粘贴 custom_pretty_clone_device_html.md 的模板在JavaScript编辑区粘贴本示例代码两处代码均以{:copy-code}标注官方文档已内置一键复制功能。代码风格约定官方示例约定控制器函数采用 PascalCase 命名如CloneDeviceDialogController并在{:code-stylemax-height: 400px;}中设置代码块展示高度这些约定在编辑器中可直接复用。实践要点关闭对话框vm.dialogRef.close(null)的入参null会作为customDialog().subscribe()的回传值可用于通知调用方结果。界面刷新保存成功后务必调用widgetContext.updateAliases()否则新设备不会实时反映到当前 Widget 的数据源中官方示例 custom-sample-js.raw 中也多处使用该调用。表单校验借助vm.validators.required与cloneDeviceFormGroup.invalid/dirty控制提交按钮状态避免空名称入库。错误处理示例未显式处理异常生产环境建议在链尾追加rxjs.catchError在失败时通过widgetContext.toastTargetId或widgetContext.dialogs给出提示并保持对话框打开。八、小结本示例虽短却是 ThingsBad 自定义 Action 领域的最小完整工程它示范了服务注入servicesMap $injector→ 动态对话框customDialog→ 表单fb/validators→ 串行数据流rxjs.mergeMap→ 界面联动updateAliases的完整范式。掌握这套组合拳后你可以据此扩展出克隆资产批量创建用户复制属性到其他实体等更多场景。相关源码可继续深入阅读 widget-component.models.tsWidgetContext 定义、custom-dialog.service.ts动态对话框实现以及官方完整示例 custom-sample-js.raw。赞分享物联网后端数据可视化消息队列【免费下载链接】thingsboardAll-in-one IoT Platform - Device management, data collection, processing and visualization.项目地址https://gitcode.com/GitHub_Trending/th/thingsboard点击查看免费下载相关推荐ThingsBoard 仪表板自定义动作实战用 customDialog 与 HTML 模板实现设备/资产创建对话框ThingsBoard 仪表板自定义动作实战用 customDialog 与 HTML 模板实现设备/资产创建对话框 本指南聚焦 ThingsBoard 仪表物联网后端数据可视化消息队列Streamflix对话框设计ShowOptions与UpdateApp对话框的实现Streamflix对话框设计ShowOptions与UpdateApp对话框的实现 概述 Streamflix作为一款Android TV应用提供了电影和OHIF UI Dialog Service 使用指南ohif/core 对话框服务的接口、实现与实战OHIF UI Dialog Service 使用指南 ohif/core 对话框服务的接口、实现与实战 UI Dialog Service 是 OHIF医疗健康前端音视频上一篇ComfyUI IPAdapter终极配置指南3步解决模型加载失败问题下一篇Android钉钉自动打卡终极方案告别迟到烦恼创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询