文档内容
DyForm
@supine/dy-form是基于Angular表单封装的动态表单库。
通过引入一组可维护的表单控件模型和动态表单控件组件,它完全自动化了表单UI创建。
@supine/dy-form 不依赖于任何Angular UI框架, 任何Angular UI框架都可以基于@supine/dy-form封装成特定风格的动态表单
@supine/dy-form 一般也不会直接在项目中使用,因为内部并没有任何控件模型,只是提供了基础表单控件的接口
目前已经适配NG-ZORRO --- @supine/dy-form-zorro
主要特性
-
解耦
- 传统的Angular表单开发需要成百上千行HTML,在组件内部维护大量与表单相关的代码,不利于后期维护,可读性不强。 @supine/dy-form 最主要的初衷就是将与表单相关的业务集中到表单模型中解决,从而减弱与组件的耦合度
-
高可读性
- 所有控件配置都在表单模型中,表单结构一目了然
-
快速开发
- 不需要成百上千行HTML,组件内部也不需要维护大量与表单相关的代码
- 大多数情况 只需要一行HTML代码和几行TS代码即可生成期望的表单
- 表单校验,一般来说只需一行代码搞定
-
面向对象
- 表单模型、控件模型都基于class实现的,表单模型、控件模型都提供了基类,可以基于基类继承进行拓展
-
易拓展性
- 轻松实现自定义控件
- 轻松实现自定义布局
- 轻松拓展内置校验规则
相关库
- 基于@supine/dy-form适配NG-ZORRO的动态表单库@supine/dy-form-zorro
- 轻量级、易拓展验证库(文档中涉及的验证参考该文档)@supine/validator
定制动态表单
基于@supine/dy-form 定制不同Angular UI框架的动态表单(自定义控件)
- 安装
ng add @supine/dy-form
-
在模块中导入 DyFormModule
-
实现控件模型(以input为例)
import {BaseModel, ModelPartial, Const} from '@supine/dy-form';
export class InputModelControl<M = any> extends BaseModel<M> {
/**
* type 必须要实现 不同的type代表不同的控件
*/
@Const('INPUT') // 表示type只能为INPUT 不允许被修改
type!: string;
/**
* 只读 如果为true 表示该控件为只读状态
*/
readonly = false;
// 如果需要其他属性 添加相应的属性即可
constructor(init?: ModelPartial<InputModelControl>) {
super();
this.init(init);
}
}
- 实现控件模型对应的装饰器(以input为例)
import {InputModelControl as Model} from '../model/input-model.control';
import {BaseDecorator, ModelPartial} from '@supine/dy-form';
export function InputModel<M>(model?: ModelPartial<Model<M>>): PropertyDecorator {
const newModel = new Model();
if (model) {
Object.assign(newModel, model);
}
return BaseDecorator(newModel);
}
- 生成一个Angular组件&并配置模板
<!--模板-->
<!--通用错误提示-->
<ng-template #errorTpl let-control>
<ng-container *ngIf="hasError(control)">
{{getError(control)}}
</ng-container>
</ng-template>
<ng-template #label let-model>
<nz-form-label [nzRequired]="model.required"
jdDyFormLabelDef
[style.flex]="dyForm?.dyFormRef.labelColLayout[dyForm.breakpoint]"
[ngClass]="dyForm.labelClass(model)"
[ngStyle]="model.labelStyle"
[nzFor]="model.controlName">{{model.label}}
</nz-form-label>
</ng-template>
<ng-template #inputControl let-model let-control=control>
<input nz-input
[id]="model.name"
[placeholder]="model.placeHolder"
[formControl]="control"
[disabled]="model.disabled"
[nzSize]="model.size"
[type]="model.inputType"
[readOnly]="model.readonly"/>
</ng-template>
<jd-dy-form [dyFormRef]="dyFormRef" #dyForm>
<!-- INPUT 就是上面实现的input模型的type属性
model 对应的就是控件模型
control 代表的是表单控件实例
-->
<nz-form-item *jdDyFormColumnDef="let control; let model = model name 'INPUT'">
<ng-template [ngTemplateOutlet]="label" [ngTemplateOutletContext]="{$implicit: model}"></ng-template>
<nz-form-control jdDyFormControlDef
[nzHasFeedback]="model.hasFeedback"
[nzExtra]="model.extra"
[nzValidatingTip]="model.validatingTip"
[ngStyle]="model.controlStyle"
[nzValidateStatus]="control"
[ngClass]="dyForm.controlClass(model)">
<ng-template [ngTemplateOutlet]="inputControl"
[ngTemplateOutletContext]="{$implicit: model, control: control}">
</ng-template>
</nz-form-control>
</nz-form-item>
<!-- 布局 可以实现各种各样的布局 在我封装的@supine/dy-form-zorro 中有3中内置布局(inline, horizontal, vertical)和一种自定义布局 -->
<jd-form-layout>
<form nz-form nzLayout="inline" [formGroup]="dyForm.formArea">
<ng-container *ngFor="let control of dyForm.options">
<ng-container jdDyFormLayoutItemName="{{control.name}}"></ng-container>
</ng-container>
</form>
</jd-form-layout>
</jd-dy-form>
// TS
import {
AfterContentInit,
ChangeDetectionStrategy,
Component,
ContentChildren,
Input,
OnInit,
QueryList,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import {DyFormColumnDef, DyFormComponent, DyFormFooterDef, DyFormHeaderDef, DyFormRef} from '@supine/dy-form';
@Component({
selector: 'jd-dy-form-zorro',
templateUrl: './dy-form-zorro.component.html',
styleUrls: ['./dy-form-zorro.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DyFormZorroComponent implements OnInit, AfterContentInit {
@ViewChild(DyFormComponent, {static: true}) dyForm: DyFormComponent;
@ContentChildren(DyFormHeaderDef, {descendants: true}) _formHeaderDefs: QueryList<DyFormHeaderDef>;
@ContentChildren(DyFormFooterDef, {descendants: true}) _formFooterDefs: QueryList<DyFormFooterDef>;
@ContentChildren(DyFormColumnDef, {descendants: true}) _formColumnDefs: QueryList<DyFormColumnDef>;
@Input() dyFormRef: DyFormRef<any>;
hasError(control: FormControl) {
return control.errors && Object.keys(control.errors).length;
}
getError(control: FormControl) {
const errors = Object.getOwnPropertyNames(control.errors);
return control.getError(errors[0]);
}
constructor() {
}
ngOnInit(): void {
}
ngAfterContentInit(): void {
// 注册form Footer 可以有多行
this._formFooterDefs.forEach(item => this.dyForm.addFooterRowDef(item));
// 注册form Header 可以有多行
this._formHeaderDefs.forEach(item => this.dyForm.addHeaderRowDef(item));
// 注册表单控件模板
this._formColumnDefs.forEach(item => this.dyForm.addColumnDef(item));
}
}
- 上面的例子我们只实现了input, 按照以上的步骤即可封装一个完整的动态表单来
使用定制好的的动态表单
- 定义表单模型
import {BaseFormModel, ValidatorRule} from '@supine/dy-form';
import {InputModel} from '../decorator/input.model';
export class LoginModel extends BaseFormModel {
@InputModel<LoginModel>({label: '用户名'})
@ValidatorRule(['required&max:15&min:4'], {required: '用户名字段是必填的', max: '用户名长度最多为15个字符', min: '用户名长度最少为4个字符'})
username = [null];
@InputModel<LoginModel>({label: '密码'})
@ValidatorRule(['required&max:15&min:4'], {required: '密码字段是必填的', max: '密码长度最多为15个字符', min: '密码长度最少为4个字符'})
password = [null];
/**
* 更新表单模型钩子
* @param formValue 当表单初始化后 formValue就为表单对象的value 否则为null
* @param model 注册了的模型配置数组 可以根据某些条件进行过滤 来动态控制表单
* @param params 调用 executeModelUpdate方法传的参数 以此来更加灵活来动态控制表单
* @return 如果返回值为void 则渲染所有注册的表单控件 如果返回表单控件数组 则只渲染该数组中的控件模型
*/
modelUpdateHook(formValue: any, model: FormControlModel[], ...params: any[]): FormControlModel[] | void {
return model;
}
/**
* 结合我封装的HTTP模块 可轻松实现批量对接与表单相关的接口
* HTTP模块 目前还没开源
* 即便不使用我封装的HTTP模块 按照以下模板 也容易实现
*/
httpRequest() {
return this.updateValueAndValidity()
.pipe(
// 如果验证未通过 则过滤掉
filter(value => value),
map(() => {
// 获取表单数据 如果不能满足需要 可以在子类重写value的获取
// const body = this.value;
// 获取提交表单的一些外部参数 比如更新的参数ID attachValue 通过 model.withAttachValue(数据)进行设置
// const {mapId, id} = this.attachValue;
//
// body.id = id;
// 组装接口所需要的参数
// const tempBody = {
// mapId,
// area_info: body
// };
// 一系列与表单相关的接口
// const httpRequestMap: HttpRequestMap = {
// update: [this.http.editAreaBaseInfo, [tempBody]]
/* UnloadMineralArea: [this.http.setUnLoadMineralArea, [body, mapId]],
UnloadWasteArea: [this.http.setUnLoadWasteArea, [body, mapId]],
LoadArea: [this.http.setLoadArea, [body, mapId]],
Road: [this.http.setRoad, [body, mapId]],
PassableArea: [this.http.setPassableArea, [body, mapId]],
ImpassableArea: [this.http.setImPassableArea, [body, mapId]],
Junction: [this.http.setJunction, [body, mapId]],
create: [this.http.createMapUtil, [body, mapId]] */
// };
// const [handle, params] = httpRequestMap[this.actionType];
//
// return handle(...params);
})
);
}
}
- 然后在HTML中声明动态表单
<jd-dy-form-zorro [dyFormRef]="dyFormRef"></jd-dy-form-zorro>
- 在组件中定义dyFormRef属性
import {Component, OnInit} from '@angular/core';
import {DyFormRef} from '@supine/dy-form';
import {FormModel} from './form.model';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
dyFormRef = new DyFormRef(FormModel);
ngOnInit(): void {
}
constructor() {
// 执行这行代码才会渲染
this.dyFormRef.executeModelUpdate();
}
}
- 至此就可以看到我们想要的表单啦
- 组件内部我们只需要维护极少数代码就能完成表单的相关操作啦
自定义控件布局
- 修改模型
import {BaseFormModel, ValidatorRule} from '@supine/dy-form';
import {InputModel} from '../decorator/input.model';
export class LoginModel extends BaseFormModel {
// 布局容器 我们可以指定自定义类型 type=phone 默认值为LAYOUT_GROUP
@LayoutGroupModel({type: 'phone'})
layout;
// parent: 'layout' 指定容器 layout
@InputModel<LoginModel>({label: '手机号码', parent: 'layout'})
@ValidatorRule(['required&phoneNum'], {required: '用户名字段是必填的', phoneNum: '请填写正确的手机号码'})
phone = [null];
@InputModel<LoginModel>({label: '用户名'})
@ValidatorRule(['required&max:15&min:4'], {required: '用户名字段是必填的', max: '用户名长度最多为15个字符', min: '用户名长度最少为4个字符'})
username = [null];
@InputModel<LoginModel>({label: '密码'})
@ValidatorRule(['required&max:15&min:4'], {required: '密码字段是必填的', max: '密码长度最多为15个字符', min: '密码长度最少为4个字符'})
password = [null];
/**
* 更新表单模型钩子
* @param formValue 当表单初始化后 formValue就为表单对象的value 否则为null
* @param model 注册了的模型配置数组 可以根据某些条件进行过滤 来动态控制表单
* @param params 调用 executeModelUpdate方法传的参数 以此来更加灵活来动态控制表单
* @return 如果返回值为void 则渲染所有注册的表单控件 如果返回表单控件数组 则只渲染该数组中的控件模型
*/
modelUpdateHook(formValue: any, model: FormControlModel[], ...params: any[]): FormControlModel[] | void {
return model;
}
/**
* 结合我封装的HTTP模块 可轻松实现批量对接与表单相关的接口
* HTTP模块 目前还没开源
* 即便不使用我封装的HTTP模块 按照以下模板 也容易实现
*/
httpRequest() {
/* .... */
}
}
- 接下来修改模板
<!--通用错误提示-->
<ng-template #errorTpl let-control>
<ng-container *ngIf="control.hasError(control.name)">
{{control.getError(control.name)}}
</ng-container>
</ng-template>
<ng-template #label let-model>
<nz-form-label [nzRequired]="model.required"
jdDyFormLabelDef
[ngStyle]="model.labelStyle"
[nzFor]="model.controlName">{{model.label}}
</nz-form-label>
</ng-template>
<jd-dy-form-zorro [dyFormRef]="dyFormRef">
<!-- phone 跟模型中的layout字段的元数据type是相对应的-->
<!-- groupModel 包含布局容器内所有字控件的配置信息 比如我要访问容器内phone控件的配置可以这样访问 groupModel.phone-->
<!-- childControl 包含布局容器内所有字控件的formControl 比如我要访问容器内phone控件可以这样访问 childControl.phone-->
<ng-container *jdDyFormColumnDef="let model = model name 'phone', let groupModel = groupInfo, let childControl = childControl">
<nz-form-item>
<ng-template [ngTemplateOutlet]="label" [ngTemplateOutletContext]="{$implicit: groupModel.phone}"></ng-template>
<nz-form-control
jdDyFormControlDef
[nzValidateStatus]="childControl.phone"
[nzErrorTip]="errorTpl"
>
<nz-input-group [nzAddOnBefore]="addOnBeforeTemplate">
<ng-template #addOnBeforeTemplate>
<nz-select class="phone-select" [ngModel]="'+86'">
<nz-option nzLabel="+86" nzValue="+86"></nz-option>
<nz-option nzLabel="+87" nzValue="+87"></nz-option>
</nz-select>
</ng-template>
<input [formControl]="childControl.phone" id="'phoneNumber'" nz-input/>
</nz-input-group>
</nz-form-control>
</nz-form-item>
</ng-container>
<!--此处省略布局代码-->
</jd-dy-form-zorro>
- 预览图如下
- 看上去挺多的 但只需要把常见的使用场景封装好了 以后开发就不要写什么模板了
自定义布局
超简单的自定义布局方式-再也不用担心动态表单布局难的问题了
<jd-dy-form [dyFormRef]="dyFormRef" #dyForm>
<nz-form-item *jdDyFormColumnDef="let control; let model = model name 'INPUT'">
<!-- 省略控件定义部分 -->
</nz-form-item>
<nz-form-item *jdDyFormColumnDef="let control; let model = model name 'INPUT_NUMBER'">
<!-- 省略控件定义部分 -->
</nz-form-item>
<!-- 省略控件定义部分 假设还有很多控件类型 -->
<jd-form-layout>
<form nz-form nzLayout="vertical" [formGroup]="dyForm.formArea">
<div>
<div>
<!-- controlName1 在表单模型中定义好的 -->
<ng-container jdDyFormLayoutItemName="controlName1"></ng-container>
</div>
</div>
<div>
<div>
<!-- controlName2 在表单模型中定义好的 -->
<ng-container jdDyFormLayoutItemName="controlName2"></ng-container>
</div>
</div>
<!-- 这个是@supine/dy-form-zorro所用的方式 -->
<!--<ng-container *ngFor="let control of dyForm.options">
<ng-container jdDyFormLayoutItemOutlet="{{control.name}}"></ng-container>
</ng-container>-->
</form>
</jd-form-layout>
</jd-dy-form>
到此自定义布局就完成了 轻轻松松布局出任何想要的布局
自定义控件
- (1)通过内置装饰器实现(更简单, 推荐临时使用的时候用该方式)
- 修改模型
import {BaseFormModel, ValidatorRule} from '@supine/dy-form'; import {InputModel} from '../decorator/input.model'; export class LoginModel extends BaseFormModel { // customPro 为自定义模型属性 将作为模板上下文属性 在模板中可以访问到 // 可以定义多个自定义属性 // type 默认为CUSTOM 允许修改 @CustomModel({label: '自定义', type: 'custom', customPro: 'customPro'}) custom = [null]; @InputModel<LoginModel>({label: '手机号码'}) @ValidatorRule(['required&phoneNum'], {required: '用户名字段是必填的', phoneNum: '请填写正确的手机号码'}) phone = [null]; @InputModel<LoginModel>({label: '用户名'}) @ValidatorRule(['required&max:15&min:4'], {required: '用户名字段是必填的', max: '用户名长度最多为15个字符', min: '用户名长度最少为4个字符'}) username = [null]; @InputModel<LoginModel>({label: '密码'}) @ValidatorRule(['required&max:15&min:4'], {required: '密码字段是必填的', max: '密码长度最多为15个字符', min: '密码长度最少为4个字符'}) password = [null]; /** * 更新表单模型钩子 * @param formValue 当表单初始化后 formValue就为表单对象的value 否则为null * @param model 注册了的模型配置数组 可以根据某些条件进行过滤 来动态控制表单 * @param params 调用 executeModelUpdate方法传的参数 以此来更加灵活来动态控制表单 * @return 如果返回值为void 则渲染所有注册的表单控件 如果返回表单控件数组 则只渲染该数组中的控件模型 */ modelUpdateHook(formValue: any, model: FormControlModel[], ...params: any[]): FormControlModel[] | void { return model; } /** * 结合我封装的HTTP模块 可轻松实现批量对接与表单相关的接口 * HTTP模块 目前还没开源 * 即便不使用我封装的HTTP模块 按照以下模板 也容易实现 */ httpRequest() { /* .... */ } }
- 接下来修改模板
<!--通用错误提示--> <ng-template #errorTpl let-control> <ng-container *ngIf="control.hasError(control.name)"> {{control.getError(control.name)}} </ng-container> </ng-template> <ng-template #label let-model> <nz-form-label [nzRequired]="model.required" jdDyFormLabelDef [ngStyle]="model.labelStyle" [nzFor]="model.controlName">{{model.label}} </nz-form-label> </ng-template> <jd-dy-form-zorro [dyFormRef]="dyFormRef"> <!-- name 'custom' 对应模型中的 type字段值--> <ng-container *jdDyFormColumnDef="let control; let model = model name 'custom'"> <nz-form-item> <ng-template [ngTemplateOutlet]="label" [ngTemplateOutletContext]="{$implicit: model}"></ng-template> <nz-form-control jdDyFormControlDef [nzValidateStatus]="control" [nzErrorTip]="errorTpl"> <!-- customPro 为模型中自定义属性--> <input type="text" nz-input [formControl]="control" [placeholder]="model.customPro"> </nz-form-control> </nz-form-item> </ng-container> <!--此处省略布局代码--> </jd-dy-form-zorro>
- (2)通过集成基类模型实现(复用的解决方案, 推荐在封装通用的控件时使用)
- 基于@supine/dy-form 定制不同Angular UI框架的动态表单 章节其实就是自定义控件
在定义控件模型的时候使用表单模型的上下文
- 模型
import {BaseFormModel, ValidatorRule} from '@supine/dy-form';
import {InputModel} from '../decorator/input.model';
export class LoginModel extends BaseFormModel {
sexModel = {
label: '性别',
optionContent: [
{label: '保密', value: 0},
{label: '男', value: 1},
{label: '女', value: 2},
]
};
// 不使用表单模型上下文是这样的
// @SelectModel<FormModel>({label: '性别', optionContent: [{label: '男', value: 1}, {label: '女', value: 1}]})
// 使用表单模型上下文是这样的 当控件模型定义比较复杂的时候用这种方法有着很大的优势
@SelectModel<FormModel>({initHook: (that, context) => Object.assign(that, context.sexModel)})
sex: [null];
@InputModel<FormModel>({label: '用户名'})
@ValidatorRule(['required&max:15&min:4'], {required: '用户名字段是必填的', max: '用户名长度最多为15个字符', min: '用户名长度最少为4个字符'})
username = [null];
@InputModel<FormModel>({label: '手机号码'})
@ValidatorRule(['required&phoneNum'], {required: '用户名字段是必填的', phoneNum: '请填写正确的手机号码'})
phone = [null];
@InputModel<LoginModel>({label: '密码'})
@ValidatorRule(['required&max:15&min:4'], {required: '密码字段是必填的', max: '密码长度最多为15个字符', min: '密码长度最少为4个字符'})
password = [null];
/**
* 更新表单模型钩子
* @param formValue 当表单初始化后 formValue就为表单对象的value 否则为null
* @param model 注册了的模型配置数组 可以根据某些条件进行过滤 来动态控制表单
* @param params 调用 executeModelUpdate方法传的参数 以此来更加灵活来动态控制表单
* @return 如果返回值为void 则渲染所有注册的表单控件 如果返回表单控件数组 则只渲染该数组中的控件模型
*/
modelUpdateHook(formValue: any, model: FormControlModel[], ...params: any[]): FormControlModel[] | void {
return model;
}
/**
* 结合我封装的HTTP模块 可轻松实现批量对接与表单相关的接口
* HTTP模块 目前还没开源
* 即便不使用我封装的HTTP模块 按照以下模板 也容易实现
*/
httpRequest() {
/* .... */
}
}
- 模板
<jd-dy-form-zorro [dyFormRef]="dyFormRef"></jd-dy-form-zorro>
自定义验证
-
自定义同步验证器
- 模型
import {BaseFormModel, ValidatorRule} from '@supine/dy-form'; import {InputModel} from '../decorator/input.model'; export class LoginModel extends BaseFormModel { /* .... */ @InputModel<LoginModel>({label: '用户名'}) @ValidatorRule(['required&max:15&min:4'], {required: '用户名字段是必填的', max: '用户名长度最多为15个字符', min: '用户名长度最少为4个字符'}) username = [null, [FormModel.userNameValidator], []]; static userNameValidator = (control: FormControl): ValidationErrors | null => { if (control.value === 'JasonWood') { // '用户名已存在' 这是用来反馈用户的 return { userName: '用户名已存在' }; } else { return null } } /* .... */ /** * 更新表单模型钩子 * @param formValue 当表单初始化后 formValue就为表单对象的value 否则为null * @param model 注册了的模型配置数组 可以根据某些条件进行过滤 来动态控制表单 * @param params 调用 executeModelUpdate方法传的参数 以此来更加灵活来动态控制表单 * @return 如果返回值为void 则渲染所有注册的表单控件 如果返回表单控件数组 则只渲染该数组中的控件模型 */ modelUpdateHook(formValue: any, model: FormControlModel[], ...params: any[]): FormControlModel[] | void { return model; } /** * 结合我封装的HTTP模块 可轻松实现批量对接与表单相关的接口 * HTTP模块 目前还没开源 * 即便不使用我封装的HTTP模块 按照以下模板 也容易实现 */ httpRequest() { /* .... */ } }
-
自定义异步验证器
- 模型
import {BaseFormModel, ValidatorRule} from '@supine/dy-form'; import {InputModel} from '../decorator/input.model'; export class LoginModel extends BaseFormModel { /* .... */ @InputModel<LoginModel>({label: '用户名'}) @ValidatorRule(['required&max:15&min:4'], {required: '用户名字段是必填的', max: '用户名长度最多为15个字符', min: '用户名长度最少为4个字符'}) username = [null, [], [FormModel.userNameAsyncValidator]]; static userNameAsyncValidator = (control: FormControl) => new Observable((observer: Observer<ValidationErrors | null>) => { setTimeout(() => { if (control.value === 'JasonWood') { // '用户名已存在' 这是用来反馈用户的 observer.next({ userName: '用户名已存在' }); } else { observer.next(null); } observer.complete(); }, 3000); }) /* .... */ /** * 更新表单模型钩子 * @param formValue 当表单初始化后 formValue就为表单对象的value 否则为null * @param model 注册了的模型配置数组 可以根据某些条件进行过滤 来动态控制表单 * @param params 调用 executeModelUpdate方法传的参数 以此来更加灵活来动态控制表单 * @return 如果返回值为void 则渲染所有注册的表单控件 如果返回表单控件数组 则只渲染该数组中的控件模型 */ modelUpdateHook(formValue: any, model: FormControlModel[], ...params: any[]): FormControlModel[] | void { return model; } /** * 结合我封装的HTTP模块 可轻松实现批量对接与表单相关的接口 * HTTP模块 目前还没开源 * 即便不使用我封装的HTTP模块 按照以下模板 也容易实现 */ httpRequest() { /* .... */ } }
-
拓展内置验证器校验规则 - 这种方式复用性强,对复用性有要求的可以用这种方式
自定义的详细描述请参考 validator
import {DY_FORM_VALIDATOR} from '@supine/dy-form';
@NgModule({
declarations: [
/* ... */
],
imports: [
/* ... */
],
providers: [
// XValidator这就是基于ZlValidator拓展的验证器
// 自定义验证规则 在定义验证规则有详细描述
{provide: DY_FORM_VALIDATOR, useValue: XValidator}
],
bootstrap: [AppComponent]
})
export class AppModule {
}
填充表单
ngOnInit(): void {
const {formValue, type} = this.data;
this.dyFormRef.executeModelUpdate();
this.dyFormRef.model // model 表单模型实例
.withAttachValue(formValue || {}) // 将要给表单赋值的数据
.withActionType(type) // 类型 比如新增或者修改
.patchValue(); // 当表单初始化后会进行表单填充
}
动态控制表单模型
modelUpdateHook(不常用,但很有用) - 动态控制表单模型
- 表单模型
import {
BaseFormModel,
ValidatorRule,
SelectOptionContent,
InputNumberGroupModel,
InputModel
} from '@supine/dy-form';
export class MapUtilFormModel extends BaseFormModel {
areaTypeContent: SelectOptionContent[] = [
{ label: '卸矿区', value: 'UnloadMineralArea' },
{ label: '装载区', value: 'LoadArea' },
{ label: '道路', value: 'Road' },
{ label: '可通行区域', value: 'PassableArea' },
{ label: '不可通行区域', value: 'ImpassableArea' }
];
_roadType = [
{ label: '主线', value: 0 },
{ label: '支线', value: 1 },
{ label: '连接线', value: 2 }
];
_capacity = [
{ label: '单车道', value: 1 },
{ label: '双车道', value: 2 }
];
// 路口类型
crossingType = [
{ label: '十字路口', value: 0 },
{ label: '丁字路口', value: 1 }
];
// 排土类型
_unloadWasteType = [
{ label: '边缘式', value: 'cliff' },
{ label: '定点式', value: 'point' }
];
// 矿物类型
mineralType = [];
@SelectModel<MapUtilFormModel>({
label: '区域类型',
initHook: (that, context) => that.optionContent = context.areaTypeContent
})
@ValidatorRule('required', { required: '请选择区域类型' })
areaType = [null];
@InputModel({ label: '名称' })
@ValidatorRule(['required&maxLength:20'], { maxLength: '最大输入20个字符', required: '请输入单元名称' })
name = [null];
// hide: true 隐藏控件
@InputModel({ label: '区域编号', hide: true })
areaId = [null];
@InputNumberGroupModel<MapUtilFormModel>({
label: '限速',
addOnAfter: 'km/h',
max: 100, min: 0,
precision: 2,
// 当类型为 ImpassableArea 限速禁用
initHook: (that, context) => that.disabled = context.actionType === 'ImpassableArea'
})
@ValidatorRule(['required&max:100&min:0'], { max: '最大值为100', min: '最小值为0', required: '请输入区域限速' })
speed = [0];
@InputNumberGroupModel<MapUtilFormModel>({
label: '车数量限制',
addOnAfter: '辆',
max: 100, min: 0,
precision: 2,
})
@ValidatorRule(['required&max:100&min:0'], { max: '最大值为100', min: '最小值为0', required: '请输入车数量限制' })
vehicleMax = [10];
@SelectModel<MapUtilFormModel>({
label: '装载类型',
initHook: (that, context) => that.optionContent = context.mineralType
})
@ValidatorRule('required', { required: '请选择装载类型' })
loadType = [null];
@SelectModel<MapUtilFormModel>({
label: '排土类型',
aliasName: 'type',
initHook: (that, context) => that.optionContent = context._unloadWasteType
})
@ValidatorRule('required', { required: '请选择装载类型' })
unloadWasteType = [null];
@InputNumberModel({ label: '排土次数上限', addOnAfter: '次' })
@ValidatorRule(['max:999'], { max: '最大排土次数999' })
unloadMax = [null];
@SelectModel<MapUtilFormModel>({
label: '卸矿类型',
initHook: (that, context) => that.optionContent = context.mineralType
})
@ValidatorRule(['required'], { required: '请选择卸矿类型' })
unloadType = [null];
@SelectModel<MapUtilFormModel>({
label: '道路类型',
aliasName: 'type',
initHook: (that, context) => that.optionContent = context._roadType
})
@ValidatorRule(['required'], { required: '请选择道路类型' })
roadType = [null];
@SelectModel<MapUtilFormModel>({
label: '容量',
initHook: (that, context) => that.optionContent = context._capacity
})
@ValidatorRule(['required'], { required: '请选择容量' })
capacity = [null];
@SelectModel<MapUtilFormModel>({
label: '路口类型',
aliasName: 'type', // 控件别名 表单真实的key 也就是获取表单value的时候该控件对应的key是type而不是junctionType
initHook: (that, context) => that.optionContent = context.crossingType
})
@ValidatorRule(['required'], { max: '请选择路口类型' })
junctionType = [null];
/**
* 更新表单模型钩子
* @param formValue 当表单初始化后 formValue就为表单对象的value 否则为null
* @param model 注册了的模型配置数组 可以根据某些条件进行过滤 来动态控制表单
* @param params 调用 executeModelUpdate方法传的参数 以此来更加灵活来动态控制表单
* @return 如果返回值为void 则渲染所有注册的表单控件 如果返回表单控件数组 则只渲染该数组中的控件模型
*/
modelUpdateHook(formValue: any, model: FormControlModel[], ...params: any[]): FormControlModel[] | void {
const typeMap = {
create: ['areaType', 'speed', 'name'], // actionType==='create' 显示 'areaType', 'speed', 'name'这几个控件 以下以此类推
ParkingLot: ['speed', 'name', 'areaId'],
UnloadMineralArea: ['speed', 'name', 'areaId'],
UnloadWasteArea: ['speed', 'name', 'unloadWasteType', 'unloadMax', 'areaId'],
// 编号、类型、名称、限速、车数量限制
LoadArea: ['areaType', 'speed', 'name'/*, 'loadType'*//*, 'areaId'*/, 'vehicleMax'],
ImpassableArea: ['speed', 'name', 'areaId'],
PassableArea: ['speed', 'name', 'areaId'],
Road: ['speed', 'name', 'roadType', 'capacity', 'areaId'],
Junction: ['speed', 'name', 'junctionType', 'areaId']
};
const controls = typeMap[this.actionType];
return model.filter(value => controls.includes(value.name));
}
/**
* 结合我封装的HTTP模块 可轻松实现批量对接与表单相关的接口
* HTTP模块 目前还没开源
* 即便不使用我封装的HTTP模块 按照以下模板 也容易实现
*/
httpRequest() {
/* .... */
}
}
- 组件
import {Component, OnInit} from '@angular/core';
import {DyFormRef} from '@supine/dy-form';
import {MapUtilFormModel} from './form.model';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
dyFormRef = new DyFormRef(MapUtilFormModel, {mode: 'vertical'});
ngOnInit(): void {
}
constructor() {
// 附加类型
this.dyFormRef.model.withActionType('create');
// 执行这行代码才会渲染 会执行表单模型中的modelUpdateHook
this.dyFormRef.executeModelUpdate();
}
}