共计 6691 个字符,预计需要花费 17 分钟才能阅读完成。
前言
之前曾经介绍过 Angular 中动静表单的根本实现思路,可参考 Angular 动静表单构建的简略实现,其中简略的介绍了前端的代码。这篇文章在之前的实现形式上做了局部批改,比方没有再应用 [ngSwitch]
而是建设了一个自定义组件配置类,而后应用动静组件的形式来渲染组件,向 对批改敞开,对扩大凋谢 的开发准则聚拢。具体能够查看源码,已在文末给出
本文将在此基础上联合 数据表构造 通过 模仿数据 来简略演示下以老师新增申请和编辑申请作为场景时,如何将数据库中的数据转换成为 v 层渲染出的表单。
最终成果外观和一般表单相似,实际上每个表单都为动静渲染失去。
一、批改阐明
这篇文章中应用的类名与属性名与 Angular 动静表单构建的简略实现中的有所区别,先在此进行简略阐明。本文中的 表单对象模型 将应用 FormInfo
为类名,动静表单模板 应用 FormComponent
为类名,动静表单内容 即表单项 应用 FieldComponent
为类名。
变动名称 | Angular 动静表单构建的简略实现 | 本文 |
---|---|---|
表单对象模型 | Form | FormInfo |
动静表单模板(表单组件) | DynamicFormComponent | FormComponent |
动静表单内容(表单项组件) | DynamicFormUnitComponent | FieldComponent |
二、问题
之前的文章是间接模仿现成的 FormInfo
(即之前文章中的Form
);这篇文章演示的是从后盾获取原始数据,而后将原始数据转换为FormInfo
,再通过FormInfos
失去相应的表单。
所以咱们要解决的次要问题是:
- 了解ER 图
- 如何将原始数据转换成
FormInfo
对象模型
三、ER 图模型
实现基本功能的动静表单的实体图如下,请对该实体图有个大略的理解。
四、ER 图含意
4.1、表单的构建
首先咱们来讨论一下 ER 图的构造,此 ER 图中有 8 个表,咱们临时先只探讨申请类型 ApplyType, 字段 Field, 字段类型 FieldType 三个表。
- 申请类型:对申请进行分类,比方评奖申请,入职申请。
- 字段:记录在数据库的表单字段,比方一个表单中的姓名输出,地址输出。
- 字段类型:字段对应的自定义类型,比方姓名应该是输出文本,那么就能够用
textbox
作为其中type
属性的值;地址能够是抉择类型,即以dropdown
为值(下面篇文章中定义的两个类型)。
每个申请类型对应多个字段,每个字段和一个字段类型相关联。
它们之间的关系就是一个申请类型就是一个表单,对应多个表单项(即字段),通过表单类型来规定表单项的模式。
4.2、表单值记录
再探讨申请表 Apply 和字段记录表 FieldRecord。下面的是针对新增的,新增实现之后须要对数据进行保留,而申请表 Apply 对应的就是新增实现之后保留的申请,通过字段记录表 FieldRecord 来记录它的值。
如同申请类型表 ApplyType 和字段表 Field 的关系,申请表 Apply 和字段表 Field 的关系也是这样,只是在之间加上了一个用来记录值的两头表。
4.3、表单验证规定
持续探讨字段校验器 FieldValidator,它记录了每个字段的校验规定,每个字段可能关联多个校验规定,比方required
,unique
。因为须要思考到字段对于验证器的要求以及用户的输出,所以在定制表单的时候须要对验证器进行范畴要求,不能让用户随便抉择验证器。所以须要先通过字段类型表 ApplyType 到字段校验器表 FieldValidator 获取到可选验证器,而后通过用户的进一步抉择,最初实现不同的字段有不同的验证规定。
4.4、字段选项
最初再加上数据集和数据项两个表,两表之间关系很简略,每个数据集对应多个数据项,次要的是在于为什么会有这两个表的存在。有一些字段类型须要进行抉择,所以要给出选项,比方 select 以及 radio 等,这时候两个表就能够发挥作用了。对于一些静态数据,比方性别 (男、女),评级(A、B、C) 等能够应用数据集来提供。对于须要实时渲染的动态数据(比方学生抉择,班级抉择),可能会须要自定义相应的组件。
对于为什么将数据集 DataSet 和字段表 Field 建设关联而不是和字段类型表 FieldType 建设关系,是想到如果须要增加的数据集多的话和后者建设关系须要创立更多的自定义组件,而如果和前者建设关系无需补充组件。
五、将原始数据转化为FormInfo
本文应用的数据都是通过事后定义好的模仿数据。
新增演示
首先筹备相应的模仿数据
import {Field} from '../entity/field';
import {Apply} from '../entity/apply';
import {FieldRecord} from '../entity/field-record';
import {DataSet} from '../entity/data-set';
import {DataItem} from '../entity/data-item';
import {ApplyType} from '../entity/apply-type';
import {FieldValidator} from '../entity/field-validator';
const MockDataItems = [{id: 1, name: '男', weight: 2, dataSet: {id: 1} as DataSet},
{id: 2, name: '女', weight: 1, dataSet: {id: 1} as DataSet},
] as DataItem[];
const MockDataSets = [{id: 1, name: '性别', dataItems: MockDataItems}
] as DataSet[];
const MockFieldValidators = [{id: 1, name: '是否必须', key: 'required', value: true}
] as FieldValidator[];
const MockFields = [{id: 1, fieldType: {id: 1, type: 'textbox'}, fieldValidators: MockFieldValidators,
key: 'name', label: '名称', weight: 4, type: 'text'},
{id: 2, fieldType: {id: 2, type: 'textbox'}, fieldValidators: MockFieldValidators,
key: 'username', label: '用户名', weight: 3, type: 'text'},
{id: 3, fieldType: {id: 3, type: 'textbox'}, fieldValidators: MockFieldValidators,
key: 'email', label: '邮箱', weight: 2, type: 'email'},
{id: 4, fieldType: {id: 4, type: 'textbox'}, fieldValidators: MockFieldValidators,
key: 'jobNumber', label: '工号', weight: 1, type: 'number'},
{id: 5, fieldType: {id: 5, type: 'radio'}, fieldValidators: MockFieldValidators,
key: 'sex', label: '性别', dataSet: MockDataSets[0], weight: 0, type: ''},
] as Field[];
export const MockApplyType = {id: 1, name: '老师新增', fields: MockFields} as ApplyType;
上述示例定义了 1 个申请类型,5 个字段,1 个验证器以及一个与男女数据项相关联的数据集。当从数据库获取这些数据之后,而后将其转换成 FormInfo,FormInfos 的中的对象个数取决于 Fields 的长度,即每一个 Field 都将转换成一个 FormInfo。
上面是转换的具体方法:
private getFormInfo(field: Field, value?: any): FormInfo<any> {
const formInfo = new FormInfo<any>({
key: field.key,
label: field.label,
weight: field.weight,
controlType: field.fieldType.type,
type: field.type,
rule: this.getRule(field.fieldValidators) as unknown as RuleType,
value
});
if (DynamicOptionControls.includes(field.fieldType.type)) {formInfo.options = field.dataSet.dataItems.map((item) => {return {value: item.id, label: item.name, weight: item.weight};
});
}
return formInfo;
}
FormInfo
类中的controlType
和type
两个属性别离代表了自定义组件的类型以及细分类型(比方 text,number);- 在这个办法中校验规定 (
rule
) 对象获取是通过获取到 MockFieldValidators 中各对象的 key 和 value 来实现的; - 办法中还须要留神的是数据集的应用,即通过事后定义的一个数组来判断对应的 field.fieldType.type(即 controlType)是否是须要增加 options 的字段类型,如果须要,那么就会将数据集中的数据项填充到这里,如果不须要则不进行填充。
- 这样就能够失去 FormInfo 的对应数组了,至于如何渲染,之前的文章曾经阐明,不过本文的实现会有局部不同,例如抉择自定义组件没有再应用
[ngSwitch]
,而是应用的动静组件实现,这里不再过多探讨,实现办法能够参考 https://worktile.com/kb/p/6517。
编辑演示
咱们在模仿数据中增加以下两项:
const MockFieldRecords = [{id: 1, apply: {id: 1}, field: MockFields[0], value: '张三'},
{id: 2, apply: {id: 1}, field: MockFields[1], value: 'zhangsan'},
{id: 3, apply: {id: 1}, field: MockFields[2], value: 'zhangsan@yunzhi.club'},
{id: 4, apply: {id: 1}, field: MockFields[3], value: '0621211001'},
{id: 4, apply: {id: 1}, field: MockFields[4], value: 1},
] as FieldRecord<any>[];
export const MockApply = {id: 1, applyType: {id: 1, name: 'teacherAdd'}, status: 0, fieldRecords: MockFieldRecords
} as Apply;
编辑和新增基本相同,只不过编辑时获取到的表单须要设置 value
初值,而这个初值就是在 FieldRecord
表中曾经记录过的 value 属性的值, 也就是模仿数据中的MockFieldRecords
。
查看之前的办法,能够发现办法中通过可选参数 value 来设置初值。
private getFormInfo(field: Field, value?: any): FormInfo<any> {
const formInfo = new FormInfo<any>({
...,
value
});
...
}
六、总结
以上是当动静表单与数据库联合起来后简略实现,文中给出了大略思路,代码波及较少,残缺的示例代码能够参考:
- github: https://github.com/chshihang/dynamic-form-er
- gitee: https://gitee.com/chshihang/dynamic-form-er
我的项目目录构造如下:
├── app
│ ├── app-routing.module.ts
│ ├── app.component.html
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── dynamic-form
│ │ ├── dynamic-form.module.ts
│ │ ├── field
│ │ │ ├── components
│ │ │ │ ├── control-type-list.config.ts
│ │ │ │ ├── dynamic-checkbox
│ │ │ │ │ ├── dynamic-checkbox.component.html
│ │ │ │ │ └── dynamic-checkbox.component.ts
│ │ │ │ ├── dynamic-dropdown
│ │ │ │ │ ├── dynamic-dropdown.component.html
│ │ │ │ │ └── dynamic-dropdown.component.ts
│ │ │ │ ├── dynamic-error
│ │ │ │ │ ├── dynamic-error.component.html
│ │ │ │ │ └── dynamic-error.component.ts
│ │ │ │ ├── dynamic-radio
│ │ │ │ │ ├── dynamic-radio.component.html
│ │ │ │ │ └── dynamic-radio.component.ts
│ │ │ │ └── dynamic-textbox
│ │ │ │ ├── dynamic-textbox.component.html
│ │ │ │ └── dynamic-textbox.component.ts
│ │ │ ├── field.component.html
│ │ │ └── field.component.ts
│ │ └── form
│ │ ├── form.component.html
│ │ └── form.component.ts
│ └── teacher
│ ├── teacher-add
│ │ ├── teacher-add.component.html
│ │ └── teacher-add.component.ts
│ ├── teacher-edit
│ │ ├── teacher-edit.component.html
│ │ └── teacher-edit.component.ts
│ ├── teacher-index
│ │ ├── teacher-index.component.html
│ │ └── teacher-index.component.ts
│ ├── teacher-routing.module.ts
│ └── teacher.module.ts
├── assets
│ └── mock-form-data.ts
├── entity
│ ├── apply-type.ts
│ ├── apply.ts
│ ├── data-item.ts
│ ├── data-set.ts
│ ├── field-record.ts
│ ├── field-type.ts
│ ├── field-validator.ts
│ ├── field.ts
│ ├── form-info.ts
│ └── rule-type.ts
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── interface
│ └── dynamic-form.interface.ts
├── main.ts
├── polyfills.ts
├── service
│ └── dynamic-form.service.ts
├── styles.scss
└── test.ts