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

前言

NG-NEST介绍

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

486f72e4ee84dc64f98759aacd307f55.png

功能分析

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

代码分析

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

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

aee17832c8317c7603e9db2a3eaa37d2.gif

欢迎 Star 了解最新信息

https://github.com/NG-NEST/ng-nest​github.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值