响应式表单:使用FormGroup, FormControl,FormArray,FormBuilder 等类构建出的数据对象就是响应式的表单,可动态删除或添加控件。
模板驱动表单:使用ngModel、ngForm进行双向绑定。
项目结构如图:
array-slave.component.html
<ng-container [formGroup]="validateForm">
<button nbButton type="button" (click)="addSlave()" style="margin-left: 2rem;font-size: 13px;padding: 7px;background-color: royalblue ;border-color: royalblue;" translate>add slave</button>
<button nbButton type="button" (click)="selectLanguage('zh-CN')" style="margin-left: 2rem;font-size: 13px;padding: 7px;background-color: saddlebrown ;border-color: saddlebrown;">中文简体</button>
<button nbButton type="button" (click)="selectLanguage('en-US')" style="margin-left: 2rem;font-size: 13px;padding: 7px;background-color: saddlebrown ;border-color: saddlebrown;">English</button>
<div formArrayName="slave" *ngFor="let slave of slave?.controls; let slaveIndex = index;">
<ng-container [formGroupName]="slaveIndex"><br>
<nb-accordion>
<nb-accordion-item #item>
<nb-accordion-item-header><span translate>Slave</span>{
{
slaveIndex+1}}
<button nbButton type="button" (click)="removeSlave(slaveIndex)" style="margin-left: 2rem;font-size: 13px;padding: 7px;background-color: royalblue ;border-color: royalblue;" translate>remove slave</button>
<span *ngIf='val'>
<span style="margin: 0 2rem;" *ngIf='val[slaveIndex]' translate>Slave Connection<span>:{
{
val[slaveIndex]["SlaveConnection"]}}</span></span>
<span *ngIf='val[slaveIndex]' translate>Hw Id<span>:{
{
val[slaveIndex]["HwId"]}}</span></span>
</span>
</nb-accordion-item-header>
<nb-accordion-item-body>
<!-- SlaveConnection TcpPort -->
<div class="form-group row">
<label class="label col-sm-2 col-form-label" >{
{
'Slave Connection' | translate }}</label>
<div class="col-sm-4">
<input type="text" nbInput fullWidth required formControlName="SlaveConnection" placeholder=""
pattern="^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$">
</div>
<label class="label col-sm-2 col-form-label" translate>Tcp Port</label>
<div class="col-sm-2">
<input type="text" nbInput fullWidth formControlName="TcpPort">
</div>
</div>
<!-- RetryCount RetryInterval -->
<div class="form-group row">
<label class="label col-sm-2 col-form-label" translate>Retry Count</label>
<div class="col-sm-2">
<input type="text" nbInput fullWidth formControlName="RetryCount">
</div>
<div class="col-sm-2"></div>
<label class="label col-sm-2 col-form-label" translate>Retry Interval</label>
<div class="col-sm-2">
<input type="text" nbInput fullWidth formControlName="RetryInterval">
</div>
</div>
<!-- HwId -->
<div class="form-group row">
<label class="label col-sm-2 col-form-label" translate>Hw Id</label>
<div class="col-sm-5">
<input type="text" nbInput fullWidth required formControlName="HwId" placeholder="PowerMeter-0a:01:01:01:01:01">
</div>
<div class="col-sm-2">
<button nbButton type="button" (click)="addopItem(slaveIndex)" style="display: inline-block;margin-left: 2rem;font-size: 12px;padding: 6px;background-color: teal;border-color: teal;" translate>add operation</button>
</div>
</div>
<!-- Operations -->
<div formArrayName="Operations" *ngFor="let item of slave.get('Operations')?.controls; let opIndex = index;">
<ng-container [formGroupName]="opIndex">
<nb-accordion>
<nb-accordion-item #item>
<nb-accordion-item-header><span translate>Operation</span>{
{
opIndex+1}}
<button nbButton type="button" (click)="removeopItem(slaveIndex,opIndex)" style="margin-left: 2rem;font-size: 12px;padding: 6px;background-color: teal;border-color: teal;" translate>remove operation</button>
</nb-accordion-item-header>
<nb-accordion-item-body>
<!-- PollingInterval UnitId -->
<div class="form-group row">
<label class="label col-sm-2 col-form-label" translate>Polling Interval</label>
<div class="col-sm-4">
<input type="text" nbInput fullWidth formControlName="PollingInterval">
</div>
<label class="label col-sm-2 col-form-label" translate>Unit Id</label>
<div class="col-sm-4">
<input type="text" nbInput fullWidth formControlName="UnitId">
</div>
</div>
<!-- StartAddress Count -->
<div class="form-group row">
<label class="label col-sm-2 col-form-label" translate>Start Address</label>
<div class="col-sm-4">
<input type="text" nbInput fullWidth formControlName="StartAddress">
</div>
<label class="label col-sm-2 col-form-label" translate>Count</label>
<div class="col-sm-4">
<input type="text" nbInput fullWidth formControlName="Count">
</div>
</div>
<!-- CorrelationId DisplayName -->
<div class="form-group row">
<label class="label col-sm-2 col-form-label" translate>Correlation Id</label>
<div class="col-sm-4">
<input type="text" nbInput fullWidth formControlName="CorrelationId">
</div>
<label class="label col-sm-2 col-form-label" translate>Display Name</label>
<div class="col-sm-4">
<input type="text" nbInput fullWidth formControlName="DisplayName">
</div>
</div>
</nb-accordion-item-body>
</nb-accordion-item>
</nb-accordion>
</ng-container>
</div>
</nb-accordion-item-body>
</nb-accordion-item>
</nb-accordion>
</ng-container>
</div>
</ng-container><br>
array-slave.component.ts
import {
Component, OnInit, Input, } from "@angular/core";
import {
FormBuilder, NgForm, FormArray, FormGroup, Validators } from "@angular/forms";
import {
TranslateService } from '@ngx-translate/core';
@Component({
selector: 'ngx-array-slave',
templateUrl: './array-slave.component.html',
styleUrls: ['./array-slave.component.scss'],
})
export class ArraySlaveComponent implements OnInit {
@Input() name: string;
@Input() form: NgForm; // 把父级的form拿到 用于插入这个父级form
public validateForm : FormGroup;
public data2:any;
// 用于在html中显示slave connection及hw id
public val:any;
constructor(private fb: FormBuilder, public translate: TranslateService) {
this.validateForm = this.fb.group({
slave : this.fb.array([
this.fb.group({
SlaveConnection: ["", Validators.required],
TcpPort: "502",
RetryCount: "10",
RetryInterval: "100",
HwId: ["", Validators.required],
Operations: this.fb.array([
this.fb.group({
PollingInterval: "",
UnitId: "",
StartAddress: "",
Count: "",
CorrelationId: "",
DisplayName: "",
})
]),
})
])
})
// 表单数据发生变化时,将data赋值给data2(valueChanges只能写在constructor中)
this.validateForm.valueChanges.subscribe(data=>{
this.data2 = data;
localStorage.setItem("slave",JSON.stringify(this.data2["slave"]));
});
}
ngOnInit() {
// 判断localstorage里是否有数据
if(localStorage.getItem("slave")){
var obj = localStorage.getItem('slave');
var objs = JSON.parse(obj);
this.val = objs;
// console.log(obj);
// 判断有几条slave,第一条赋值,后面的添加到表单中
for(var i=0;i<objs.length;i++){
if(i==0){
this.slave.setValue([{
SlaveConnection: objs[0]["SlaveConnection"],
TcpPort: objs[0]["TcpPort"],
RetryCount: objs[0]["RetryCount"],
RetryInterval: objs[0]["RetryInterval"],
HwId: objs[0]["HwId"],
Operations: ([{
PollingInterval: "",
UnitId: "",
StartAddress: "",
Count: "",
CorrelationId: "",
DisplayName: "",
}])
}]);
var ops = objs[i]["Operations"];
if(ops.length > 0){
for(var j=0;j<ops.length;j++){
if(j==0){
(this.slave.at(i).get('Operations') as FormArray).setValue([{
PollingInterval: ops[j]["PollingInterval"],
UnitId: ops[j]["UnitId"],
StartAddress: ops[j]["StartAddress"],
Count: ops[j]["Count"],
CorrelationId: ops[j]["CorrelationId"],
DisplayName: ops[j]["DisplayName"],
}])
}else{
(this.slave.at(i).get('Operations') as FormArray).push(
this.fb.group({
PollingInterval: ops[j]["PollingInterval"],
UnitId: ops[j]["UnitId"],
StartAddress: ops[j]["StartAddress"],
Count: ops[j]["Count"],
CorrelationId: ops[j]["CorrelationId"],
DisplayName: ops[j]["DisplayName"],
})
)
}
}
}else{
(this.slave.at(i).get('Operations') as FormArray).removeAt(0);
}
}else{
this.slave.push(
this.fb.group({
SlaveConnection: objs[i]["SlaveConnection"],
TcpPort: objs[i]["TcpPort"],
RetryCount: objs[i]["RetryCount"],
RetryInterval: objs[i]["RetryInterval"],
HwId: objs[i]["HwId"],
Operations: this.fb.array([
this.fb.group({
PollingInterval: "",
UnitId: "",
StartAddress: "",
Count: "",
CorrelationId: "",
DisplayName: "",
})
]),
})
);
var ops = objs[i]["Operations"];
if(ops.length > 0){
for(var j=0;j<ops.length;j++){
if(j==0){
(this.slave.at(i).get('Operations') as FormArray).setValue([{
PollingInterval: ops[j]["PollingInterval"],
UnitId: ops[j]["UnitId"],
StartAddress: ops[j]["StartAddress"],
Count: ops[j]["Count"],
CorrelationId: ops[j]["CorrelationId"],
DisplayName: ops[j]["DisplayName"],
}])
}else{
(this.slave.at(i).get('Operations') as FormArray).push(
this.fb.group({
PollingInterval: ops[j]["PollingInterval"],
UnitId: ops[j]["UnitId"],
StartAddress: ops[j]["StartAddress"],
Count: ops[j]["Count"],
CorrelationId: ops[j]["CorrelationId"],
DisplayName: ops[j]["DisplayName"],
})
)
}
}
}else{
(this.slave.at(i).get('Operations') as FormArray).removeAt(0);
}
}
}
}
// 将validateForm表单传给父级的form
this.form.control.addControl(this.name, this.validateForm);
}
get slave() {
return this.validateForm.get("slave") as FormArray;
}
// slave增加及删除
addSlave(): void{
this.slave.push(
this.fb.group({
SlaveConnection: ["", Validators.required],
TcpPort: "502",
RetryCount: "10",
RetryInterval: "100",
HwId: ["", Validators.required],
Operations: this.fb.array([
this.fb.group({
PollingInterval: "",
UnitId: "",
StartAddress: "",
Count: "",
CorrelationId: "",
DisplayName: "",
})
]),
})
)
}
removeSlave(index: number) {
this.slave.removeAt(index);
}
// op增加及删除
addopItem(slaveIndex: number): void{
(this.slave.at(slaveIndex).get('Operations') as FormArray).push(
this.fb.group({
PollingInterval: "",
UnitId: "",
StartAddress: "",
Count: "",
CorrelationId: "",
DisplayName: "",
})
)
}
removeopItem(slaveIndex: number,opIndex: number) {
(this.slave.at(slaveIndex).get('Operations') as FormArray).removeAt(opIndex);
}
// 设置语言
public selectLanguage(lang) {
this.translate.use(lang);
// 更新当前记录的语言
localStorage.setItem('currentLanguage', lang)
}
}
array-slave.component.scss
nb-accordion{
// 必须填写的样式
// .ng-valid[required], .ng-valid.required{
// border-left: 5px solid #42a948;
// }
.ng-invalid:not(form){
border-left: 5px solid #a94442;
}
}
modbus.component.html
<div class="row">
<div class="col-md-9">
<nb-card>
<nb-card-header>modbus协议</nb-card-header>
<nb-card-body style="padding: 0.5rem;">
<form novalidate style="margin-bottom: 1rem;" #slavesForm="ngForm">
<ngx-array-slave name="slaves" [form]="slavesForm"></ngx-array-slave>
<!-- 保存 -->
<button nbButton type="button" [disabled]="!slavesForm?.valid" (click)="onSubmit(slavesForm)" style="margin-left: 2rem;margin-top: 0.5rem;font-size: 13px;padding: 7px;" translate>submit</button>
</form>
</nb-card-body>
</nb-card>
</div>
</div>
modbus.component.ts
import {
Component, OnInit, SimpleChanges } from '@angular/core';
import {
NgForm } from '@angular/forms';
import {
HttpClient, HttpHeaders } from '@angular/common/http';
@Component({
selector: 'ngx-modbus',
templateUrl: './modbus.component.html',
styleUrls: ['./modbus.component.scss']
})
export class ModbusComponent implements OnInit {
private SlaveConfigs;
// get请求数据 - 获取数组中数据条数
public list:any={
};
public num:any;
constructor(public http:HttpClient) {
let api = 'assets/admin-data/system-configuration.json';
this.http.get(api).subscribe(resp =>{
var propertiesdesired = resp['properties.desired'];
this.SlaveConfigs = propertiesdesired["SlaveConfigs"];
// 计算json数据中slave的个数
var count=0;
for(var key in this.SlaveConfigs){
count++;
}
this.num = count;
// console.log(this.num);
});
}
ngOnInit() {
}
// 提交表单
onSubmit(form: NgForm){
// 获取表单数据
const params = form.control.get('slaves').get('slave').value;
// console.log(params);
// 判断是否有slave
if(params.length == 0){
alert("您没有添加Slave");
}
// 判断是否已经提交了256条slave,如果num >= 256
if(this.num >= 256){
alert("您已添加256条Slave");
}else{
// 请求头
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
};
for(var i=0;i<params.length;i++){
// post提交数据 - json-server建虚拟服务器环境
let api = 'http://localhost:3000/properties.desired';
// op01 op01...
var Operations = {
};
var ops = params[i]["Operations"];
var opNum = ops.length;
for(var j=0; j<opNum; j++){
let num2:any = j+1;
if(num2 < 10){
num2 = '0' + num2;
}else{
num2 = num2;
}
Operations["Op"+num2] = ops[j]
// console.log(ops[j]);
}
// console.log(opNum, ops);
params[i]["Operations"] = Operations;
// console.log(this.Operations);
this.num ++;
// 改为slave01 02 03...格式
if(this.num < 10){
this.num = '0' + this.num;
}else{
this.num = this.num;
}
this.SlaveConfigs['Slave'+this.num] = params[i];
// console.log(this.SlaveConfigs);
this.http.patch(api, {
"SlaveConfigs" : this.SlaveConfigs}, httpOptions).subscribe(resp =>{
});
}
// 将localstorage中缓存的数据删除
localStorage.removeItem("slave");
}
}
}
modbus.component.scss
nb-card{
// 必须填写的样式
// .ng-valid[required], .ng-valid.required{
// border-left: 5px solid #42a948;
// }
.ng-invalid:not(form){
border-left: 5px solid #a94442;
}
// op1 op2样式
.opStyle{
font-weight: 600;
display: inline-block;
width: 100%;
margin-bottom: 1.2rem;
padding-bottom: 0.8rem;
border-bottom: 1px solid #edf1f7;
}
}
使用json-server虚拟服务器
npm install -g json-server
json-server --watch modbus.json
modbus.json
{
"properties.desired": {
"PublishInterval": "5000",
"Version": "1",
"SlaveConfigs": {
}
}
}
在项目的assets目录下新建i18n文件夹,如图:
en-US.json
{
"Slave":"Slave",
"add slave": "add slave",
"remove slave": "remove slave",
"Slave Connection": "Slave Connection",
"Tcp Port": "Tcp Port",
"Retry Count": "Retry Count",
"Retry Interval": "Retry Interval",
"Hw Id": "Hw Id",
"Operation":"Operation",
"add operation":"add operation",
"remove operation":"remove operation",
"Polling Interval": "Polling Interval",
"Unit Id": "Unit Id",
"Start Address": "Start Address",
"Count": "Count",
"Correlation Id": "Correlation Id",
"Display Name": "Display Name",
"submit":"submit"
}
zh-CN.json
{
"Slave":"从机",
"add slave": "添加从机",
"remove slave": "移除从机",
"Slave Connection": "从机连接",
"Tcp Port": "tcp端口",
"Retry Count": "重试计数",
"Retry Interval": "重试间隔",
"Hw Id": "硬件标识",
"Operation":"操作",
"add operation":"添加操作",
"remove operation":"删除操作",
"Polling Interval": "轮询间隔",
"Unit Id": "单位编号",
"Start Address": "起始地址",
"Count": "总数",
"Correlation Id": "关联ID",
"Display Name": "显示名称",
"submit":"提交"
}