前言

之前曾经介绍过Angular中动静表单的根本实现思路,可参考Angular动静表单构建的简略实现,其中简略的介绍了前端的代码。这篇文章在之前的实现形式上做了局部批改,比方没有再应用[ngSwitch]而是建设了一个自定义组件配置类,而后应用动静组件的形式来渲染组件,向对批改敞开,对扩大凋谢的开发准则聚拢。具体能够查看源码,已在文末给出

本文将在此基础上联合数据表构造通过模仿数据来简略演示下以老师新增申请和编辑申请作为场景时,如何将数据库中的数据转换成为v层渲染出的表单。
最终成果外观和一般表单相似,实际上每个表单都为动静渲染失去。



一、 批改阐明

这篇文章中应用的类名与属性名与Angular动静表单构建的简略实现中的有所区别,先在此进行简略阐明。本文中的表单对象模型将应用FormInfo为类名,动静表单模板应用FormComponent为类名,动静表单内容表单项应用FieldComponent为类名。

变动名称Angular动静表单构建的简略实现本文
表单对象模型FormFormInfo
动静表单模板(表单组件)DynamicFormComponentFormComponent
动静表单内容(表单项组件)DynamicFormUnitComponentFieldComponent

二、问题

之前的文章是间接模仿现成的FormInfo(即之前文章中的Form);这篇文章演示的是从后盾获取原始数据,而后将原始数据转换为FormInfo,再通过FormInfos失去相应的表单。
所以咱们要解决的次要问题是:

  1. 了解ER图
  2. 如何将原始数据转换成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,它记录了每个字段的校验规定,每个字段可能关联多个校验规定,比方requiredunique。因为须要思考到字段对于验证器的要求以及用户的输出,所以在定制表单的时候须要对验证器进行范畴要求,不能让用户随便抉择验证器。所以须要先通过字段类型表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类中的controlTypetype两个属性别离代表了自定义组件的类型以及细分类型(比方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