前言
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
#form
class="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、XSelectComponent
Object.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-nestgithub.com