Angular有两种类型的表单:响应式表单(reactive forms)、模板驱动表单(Template-driven forms)。
共同基础

  • [FormControl](https://angular.cn/api/forms/FormControl) 实例用于追踪单个表单控件的值和验证状态。
  • [FormGroup](https://angular.cn/api/forms/FormGroup) 用于追踪一个表单控件组的值和状态。
  • [FormArray](https://angular.cn/api/forms/FormArray) 用于追踪表单控件数组的值和状态。
  • [ControlValueAccessor](https://angular.cn/api/forms/ControlValueAccessor) 用于在 Angular 的 [FormControl](https://angular.cn/api/forms/FormControl) 实例和原生 DOM 元素之间创建一个桥梁。

实现自定义表单控件的核心在于:[ControlValueAccessor](https://angular.cn/api/forms/ControlValueAccessor),组件需要实现这个接口的功能。

  1. interface ControlValueAccessor {
  2. writeValue(obj: any): void
  3. registerOnChange(fn: any): void
  4. registerOnTouched(fn: any): void
  5. setDisabledState(isDisabled: boolean)?: void
  6. }
  • writeValue(obj: any):该方法用于将模型中的新值写入视图或 DOM 属性中。
  • registerOnChange(fn: any):设置当控件接收到 change 事件后,调用的函数
  • registerOnTouched(fn: any):设置当控件接收到 touched 事件后,调用的函数
  • setDisabledState?(isDisabled: boolean):当控件状态变成 DISABLED 或从 DISABLED 状态变化成 ENABLE 状态时,会调用该函数。该函数会根据参数值,启用或禁用指定的 DOM 元素。

在组件实现[ControlValueAccessor](https://angular.cn/api/forms/ControlValueAccessor)接口后,要能正常使用的话,还需要执行注册操作。

实现ngModel双向绑定

导入FormsModule

  1. import { NgModule } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { FormsModule } from '@angular/forms';
  4. import { NgZorroAntdModule } from 'ng-zorro-antd';
  5. import { City3Component } from './city3.component';
  6. @NgModule({
  7. imports: [CommonModule, FormsModule, NgZorroAntdModule],
  8. declarations: [City3Component],
  9. exports: [City3Component],
  10. })
  11. export class City3Module {}

组件需要注入一个Provider去实现。

  1. /**
  2. * template-driven forms 模板驱动表单
  3. * 实现 ngModel
  4. */
  5. const EXE_CITY3_VALUE_ACCESSOR: ExistingProvider = {
  6. provide: NG_VALUE_ACCESSOR,
  7. useExisting: forwardRef(() => City3Component),
  8. multi: true,
  9. };
  10. @Component({
  11. selector: 'app-city3',
  12. templateUrl: './city3.component.html',
  13. styleUrls: ['./city3.component.less'],
  14. encapsulation: ViewEncapsulation.None,
  15. changeDetection: ChangeDetectionStrategy.OnPush,
  16. providers: [EXE_CITY3_VALUE_ACCESSOR]
  17. })
  18. export class City3Component implements OnInit, ControlValueAccessor {
  19. // ...
  20. }

实现formControl

导入ReactiveFormsModule

  1. import { NgModule } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { ReactiveFormsModule } from '@angular/forms';
  4. import { NgZorroAntdModule } from 'ng-zorro-antd';
  5. import { City3Component } from './city3.component';
  6. @NgModule({
  7. imports: [CommonModule, ReactiveFormsModule, NgZorroAntdModule],
  8. declarations: [City3Component],
  9. exports: [City3Component],
  10. })
  11. export class City3Module {}

实现自定义校验器

  • 使用 ValueProvider 注册
  1. /**
  2. * 直接提供给provider使用的校验器
  3. * @param control AbstractControl
  4. */
  5. const validateSelectValue: ValidatorFn = (control: AbstractControl): ValidationErrors => {
  6. if (control.touched) {
  7. const reg = CITY_REGEXPS[2];
  8. if (control.value) {
  9. return reg.test(control.value) ? null : { invalid: true };
  10. } else {
  11. return null;
  12. }
  13. }
  14. };
  15. /**
  16. * provider 默认校验使用的方式
  17. */
  18. const EXE_CITY3_VALIDATOR: ValueProvider = {
  19. provide: NG_VALIDATORS,
  20. useValue: validateSelectValue,
  21. multi: true,
  22. };
  23. @Component({
  24. selector: 'app-city3',
  25. templateUrl: './city3.component.html',
  26. styleUrls: ['./city3.component.less'],
  27. encapsulation: ViewEncapsulation.None,
  28. changeDetection: ChangeDetectionStrategy.OnPush,
  29. providers: [EXE_CITY3_VALIDATOR]
  30. })
  31. export class City3Component implements OnInit, ControlValueAccessor {
  32. // ...
  33. }
  • 使用 ExistingProvider 注册
  1. /**
  2. * 自定义校验所使用的校验方式,组件要额外实现 Validator
  3. */
  4. const EXE_CITY3_VALIDATOR_2: ExistingProvider = {
  5. provide: NG_VALIDATORS,
  6. useExisting: forwardRef(() => City3Component),
  7. multi: true,
  8. };
  9. @Component({
  10. selector: 'app-city3',
  11. templateUrl: './city3.component.html',
  12. styleUrls: ['./city3.component.less'],
  13. encapsulation: ViewEncapsulation.None,
  14. changeDetection: ChangeDetectionStrategy.OnPush,
  15. providers: [EXE_CITY3_VALIDATOR_2]
  16. })
  17. export class City3Component implements OnInit, ControlValueAccessor, Validator {
  18. // ...
  19. /**
  20. * 实现自定义校验,写在组件类可介入入参
  21. * @param control AbstractControl
  22. */
  23. validate(control: AbstractControl): ValidationErrors | null {
  24. if (control.touched) {
  25. const reg = CITY_REGEXPS[2];
  26. if (control.value) {
  27. return reg.test(control.value) ? null : { invalid: true };
  28. } else {
  29. return null;
  30. }
  31. }
  32. }
  33. }

参考:https://segmentfault.com/a/1190000009070500https://github.com/NG-ZORRO/ng-zorro-antd/tree/master/components/select
自定义表单控件完整代码:https://github.com/superchow/ng-login-demo/tree/master/src/app/components/city3