前言

NG-NEST介绍

今天我们来看一下 Form 表单组件是如何实现的:

功能分析

  • 由不同的表单控件组成(输入框、选择器、单选框、多选框等)
  • 控件的禁用、必填、正则验证等状态
  • 标题和控件位置,局部分类,栅格布局等

代码分析

lib/ng-nest/ui/form
├── docs                        md 文档
├── examples                    示例
├── style                       样式 @mixin 和 样式参数定义
├── control.component.html
├── control.component.ts        控件组件(映射不同类型的表单组件)
├── form.component.html
├── form.component.scss
├── form.component.ts
├── form.component.spec.ts      测试文件,测试模式下直接访问对应的关键子调试单个组件
├── form.module.ts              组件模块,声明模块中的视图,依赖的模块,以及导出的视图
├── form.property.ts            组件属性( Input 输入和 Ouput 输出参数),以及相关的类型定义,文档中的组件参数说明通过此处生成
├── index.ts
├── package.json                ng-packagr 配置,单独引入组件
└── public-api.ts               组件文件以及API

我们先看 form.component.html 文件:

<!--
使用 formGroup 指令指定一个表单对象,并指定相关样式
controlsType 用来区分 controls 中是否包含了行的分类,通过 ngSwitch 来指定不同的模板
-->
<form#formclass="x-form"[formGroup]="formGroup"[style.width]="width"[style.padding-bottom.rem]="controlsType === 'controls' ? this.space : 0"[ngClass]="classMap"
><ng-container [ngSwitch]="controlsType"><ng-container *ngSwitchCase="'controls'"><ng-container *ngTemplateOutlet="controlsTemp; context: { controls: controls }"> </ng-container></ng-container><ng-container *ngSwitchCase="'rows'"><ng-container *ngTemplateOutlet="rowsTemp; context: { rows: controls }"></ng-container></ng-container></ng-container>
</form><!--
行模板,先加载行分类,在加载行中对应的控件
通过行参数 hidden 可以隐藏整行
-->
<ng-template #rowsTemp let-rows="rows"><ng-container *ngFor="let row of rows"><x-row [hidden]="row.hidden"><ng-container *ngTemplateOutlet="titleTemp; context: { row: row }"></ng-container><ng-container *ngTemplateOutlet="controlsTemp; context: { controls: row.controls }"></ng-container></x-row></ng-container>
</ng-template><!-- 行中控件 -->
<ng-template #controlsTemp let-controls="controls"><x-row [space]="space"><x-col[style.padding-top.rem]="space"[span]="!control.span ? span : control.span"*ngFor="let control of controls"[hidden]="control.hidden"><x-control [option]="control"></x-control></x-col></x-row>
</ng-template><!-- 行标题模板 -->
<ng-template #titleTemp let-row="row"><label class="x-form-title"><x-icon *ngIf="row.icon" [type]="row.icon"></x-icon><span>{{ row.title }}</span></label>
</ng-template>

对应的 form.component.ts 文件:

export class XFormComponent extends XFormProperty implements OnInit {// 用来判断传递的控件参数中是否包含行controlsType: 'controls' | 'rows';// 用来存储所有的控件组件controlComponents: { [property: string]: XFormControlComponent } = {};// 用来存储所有的控件类型controlTypes: { [property: string]: XFormControlType } = {};constructor(public cdr: ChangeDetectorRef, public configService: XConfigService) {super();}ngOnChanges(changes: SimpleChanges) {XIsChange(changes.disabled) && this.setDisabled();}ngOnInit() {this.setControls();this.setClassMap();}ngAfterViewInit() {this.setDisabled();}setControls() {if (this.controls && this.controls.length > 0) {this.controlsType = this.controls[0].controls ? 'rows' : 'controls';}}setClassMap() {this.classMap[`${XFormPrefix}-${this.controlsType}`] = true;}// 通过表单禁用/启用所有控件,并处理相关的必填、正则验证setDisabled() {if (Object.keys(this.controlComponents).length === 0) return;if (this.disabled) {for (let key in this.controlComponents) {let [control, type] = [this.controlComponents[key], this.controlTypes[key]];control.disabled = true;control.required = false;delete control.pattern;type.setValidators();control.formControlChanges();}} else {for (let key in this.controlComponents) {let [control, type] = [this.controlComponents[key], this.controlTypes[key]];control.disabled = type.disabled as XBoolean;control.required = type.required as XBoolean;control.pattern = type.pattern as RegExp | RegExp[];type.setValidators();control.formControlChanges();}}this.formGroup.updateValueAndValidity();}// 获取表单中控件的验证信息getValidatorMessages(): string[] {let result: string[] = [];if (this.formGroup.valid) return result;else {const eachControls = (array: XFormControlOption[]) => {for (const ctr of array) {const formCtr = this.formGroup.controls[ctr.id] as XFormControl;if (formCtr && formCtr.invalid) {result = [...result, ...(formCtr.messages as string[])];}}};if (this.controlsType === 'rows') {for (const row of this.controls as XFormRow[]) {eachControls(row.controls);}} else {eachControls(this.controls as XFormControlOption[]);}}return result;}
}

form 组件主要是用来组织表单结构,并提供了全局禁用的方式以及获取验证信息的方法。

接下来我们看控件组件 control.component.html :

 <div class="x-control" [formGroup]="form?.formGroup"><ng-container [ngSwitch]="option?.control"><ng-container *ngSwitchCase="'input'"><x-input [formControlName]="option.id" (clearEmit)="option.clearClick && option.clearClick($event)"></x-input></ng-container><ng-container *ngSwitchCase="'select'"><x-select [formControlName]="option.id"></x-select></ng-container>......<ng-container *ngSwitchCase="'find'"><x-find [formControlName]="option.id"></x-find></ng-container></ng-container>
</div>

  • form 对应我们的表单父组件
  • 使用 ngSwitch 来指定不同的组件
  • 在使用各种组件的时候只定义了 formControlName 的名字,并且只对输出参数做了处理,输入参数在对应的 ts 文件里面直接映射进去
export class XControlComponent extends XControlProperty implements OnInit, AfterViewInit, OnDestroy {// 控件参数@Input() option: XFormControlOption;// 通过 FormControlName 获取对应的控件@ViewChild(FormControlName, { static: false }) control: FormControlName;// 共享属性,可以通过表单参数只指定一次,单个控件中不需要再指定private _sharedProps = ['span', 'direction', 'justify', 'align', 'labelWidth', 'labelAlign'];// 改变的属性,此处可以调用参数中的 change 事件来触发组件的更新 private _changeProps = ['label', ...this._sharedProps];// 控件类型private _control: XFormControlType;// 验证类型private _validatorFns: ValidatorFn[] = [];private _unSubject = new Subject();// 根据 option 创建的 FormControl 对象 private _formControl: FormControl;constructor(@Host() @Optional() public form: XFormComponent, public cdr: ChangeDetectorRef, public configService: XConfigService) {super();}ngOnInit() {// 共享属性设置this.setProps();// label 后缀设置this.option.label = `${this.option.label}${this.form.labelSuffix}`;// 创建控件类型this._control = this.createControl(this.option);// 创建控件对象this._formControl = new FormControl(this._control.value);// 验证设置this.setValidators();// 订阅状态变化事件,用来设置显示信息(主要正则验证或错误信息)this._formControl.statusChanges.pipe(takeUntil(this._unSubject)).subscribe((x) => {this.setMessages(x);});// 定义类型中的验证事件this._control.setValidators = () => this.setValidators();// formGroup 中添加表单对象this.form.formGroup.addControl(this._control.id, this._formControl);// 定义参数中的 change 事件this.option.change = () => {this._changeProps.forEach((x: string) => {if (this.control.valueAccessor && this.option[x]) {(this.control.valueAccessor as any)[x] = this.option[x];}});this.form.controlComponents[this._control.id].formControlChanges();};}ngAfterViewInit() {// 映射具体控件中的输入参数// valueAccessor 指向我们事件的表单控件对象,比如 XInputComponent、XSelectComponentObject.assign(this.control.valueAccessor, this._control);// 把当前控件注册到 form 父组件中,并执行对应组件改变事件this.form.controlTypes[this._control.id] = this._control;this.form.controlComponents[this._control.id] = this.control.valueAccessor as XFormControlComponent;this.form.controlComponents[this._control.id].formControlChanges();}ngOnDestroy() {this._unSubject.next();this._unSubject.unsubscribe();}// 控件验证setValidators() {this._validatorFns = [];// 禁用if (this._control.disabled || this.form.disabled) {this._formControl.disable();} else {this._formControl.enable();}// 必填if (this._control.required && !this.form.disabled) {this._validatorFns = [...this._validatorFns, Validators.required];}// 正则if (this._control.pattern) {this.setPattern();}// 重新设置并更新验证信息this._formControl.setValidators(this._validatorFns);this._formControl.updateValueAndValidity();}// 设置共享属性setProps() {for (let prop of this._sharedProps) {if (XIsEmpty(this.option[prop])) this.option[prop] = (this.form as any)[prop];}}// 设置正则验证setPattern() {if (Array.isArray(this._control.pattern)) {for (const pt of this._control.pattern) {this._validatorFns = [...this._validatorFns, Validators.pattern(pt)];}} else {this._validatorFns = [...this._validatorFns, Validators.pattern(this._control.pattern as RegExp)];}}// 设置正则验证的信息getPatternMsg(pattern: string) {if (Array.isArray(this._control.pattern)) {return (this._control.message as Array<any>)[this._control.pattern.findIndex((x) => String(x) === pattern)];} else {return this._control.message;}}setMessages(state: 'INVALID' | 'VALID' | 'DISABLED') {let control: XFormControl = this._formControl;if (state === 'INVALID' && this._formControl.errors !== null) {for (const key in control.errors) {if (key === 'required') {control.messages = [`${this._control.label} 必填`];} else if (key === 'pattern') {control.messages = [`${this._control.label} ${this.getPatternMsg(control.errors[key].requiredPattern)}`];}}} else if (state === 'VALID') {control.messages = [];}}// 创建对应类型的控件createControl(option: XFormControlOption) {switch (option.control) {case 'input':return new XInputControl(option as XInputControlOption);case 'select':return new XSelectControl(option as XSelectControlOption);case 'checkbox':return new XCheckboxControl(option as XCheckboxControlOption);case 'radio':return new XRadioControl(option as XRadioControlOption);case 'switch':return new XSwitchControl(option as XSwitchControlOption);case 'rate':return new XRateControl(option as XRateControlOption);case 'date-picker':return new XDatePickerControl(option as XDatePickerControlOption);case 'time-picker':return new XTimePickerControl(option as XTimePickerControlOption);case 'input-number':return new XInputNumberControl(option as XInputNumberControlOption);case 'slider-select':return new XSliderSelectControl(option as XSliderSelectControlOption);case 'cascade':return new XCascadeControl(option as XCascadeControlOption);case 'color-picker':return new XColorPickerControl(option as XColorPickerControlOption);case 'find':return new XFindControl(option as XFindControlOption);default:return new XInputControl(option as XInputControlOption);}}
}

总结

以上就是 Form 表单组件的解析,控件组件主要用来映射具体的表单组件(输入框、选择框、多选框、单选框等),并在此基础上提供了通用属性以及方法。

下一次将介绍 国际化 实现原理:

欢迎 Star 了解最新信息

https://github.com/NG-NEST/ng-nest​github.com

angular select2源码解析_Angular 组件库 NG-NEST 源码解析:Form 表单组件相关推荐

  1. 【实习小tip】多层dialog弹窗遮罩问题、elementUI的form表单组件的select框在只读的情况下没办法拿到传来的数据、从弹窗子组件获取数据后需要刷新页面

    解决elementui多层dialog弹窗遮罩问题 弹窗套娃出现了整个屏幕都是遮罩层的问题,需要鼠标点击一下才能正常. 在弹窗组件代码上加上 append-to-body 就可以了,表示这个弹窗是嵌在 ...

  2. 使用vue表单验证库async-validator封装Form表单组件

    src/components/data/seller/create/contract.vue <template><create-portlet title="合同信息&q ...

  3. Django Form表单组件

    Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否 ...

  4. 通过json配置生成form表单,vue3+ts+elementPlus,form表单组件封

    子组件 //src\components\form\index.vue <script setup lang="ts"> import { PropType, ref, ...

  5. 如何用JavaScript操作form表单组件?

    一.用JavaScript操作按钮: <!DOCTYPE html> <html><head><meta charset="UTF-8"& ...

  6. Django之form表单组件、cookie与session

    ---恢复内容开始--- Form表单组件 引例: 先来看一个注册的例子,全部用的是reg函数来实现的. views.py文件 def reg(request):errors = {'username ...

  7. 从Antd 源码到自我实现之 Form表单

    前言 Antd 中的组件大部分基于蚂蚁金服的组件库 react-component.antd 与 react-component 都是开源项目,阅读其源码可以给我们带来很多收益,比如: 了解各式各样的 ...

  8. UI组件库Form表单_数字类型验证之坑实现数字框

    目录 Input 输入框 实现数字框封装使用 项目需求 : 使用的饿了么组件库的 input 框 , 但是想要 实现用户只能输入 数字 的功能 , So 看到了数字类型的验证 (缺点 :不能阻止用户输 ...

  9. 基于dumi和antd封装的易用组件库(每个人都应该有自己的组件库)

    首先说下哈,不是造轮子,从来也不提倡造轮子- 作为一名前端程序员,个人感觉轮子已经够多了,留下来的时间不如摸鱼,比如发发沸点.逛逛掘金,或者下班了陪陪女朋友 最近发现了一个好的工具 dumi 突然心血 ...

最新文章

  1. oracle11g分区表按时间自动创建
  2. 【转载】Unix编程艺术——Unix哲学
  3. 查看oracle自定义函数,Oracle自定义函数查询数据字典项
  4. python 将实例用作属性_将类实例用作类属性、描述符和属性
  5. 缓存-分布式锁-Redisson简介整合
  6. 个人企业作品网站导航页源码
  7. 题目1198:a+b
  8. 安装brew_MacBook Pro安装Homebrew慢的问题解决方案
  9. Linux中用yum安装MySQL方法
  10. oracle database 10g rman备份与恢复pdf,Oracle Database10g RMAN备份与恢复
  11. 华为21天云计算培训
  12. SECS半导体通信委员会参考书
  13. iMazing v2021绿色便携版iOS设备数据管理工具
  14. 蓝牙相关学习:5.BLE协议属性协议层(ATT)
  15. python 学术论文,python论文_python 论文_python
  16. python xgb模型 预测_如何使用XGBoost模型进行时间序列预测
  17. ssh mysql jsp码头船只出行及配套货柜码放管理系统的设计与实现
  18. 【SDN】软件定义硬件
  19. eclipse配置opencv和javacv环境
  20. [从头读历史] 第275节 诗经 秦风

热门文章

  1. 德国精品软件推荐   压缩软件 WINRAR 个人版终于免费了。
  2. SCCM 2012 R2 从入门到精通 Part11 系统推送(2)
  3. 常用数据结构有哪些(转)
  4. Ghost for linux 工具备份还原系统
  5. 关于Keil4 转到 Keil5以后的一些错误解决
  6. 七种实现左侧固定,右侧自适应两栏布局的方法
  7. Java后台通过jxl生成Excel表格
  8. Java实现HTML页面转PDF解决方案(转)
  9. Extension Method - c#3.0
  10. linux下远程访问Mysql