文章转自我的语雀:https://www.yuque.com/liuyin-zzwa0/angular/custom-form-control
Angular有两种类型的表单:响应式表单(reactive forms)、模板驱动表单(Template-driven forms)。
共同基础
- FormControl 实例用于追踪单个表单控件的值和验证状态。
- FormGroup 用于追踪一个表单控件组的值和状态。
- FormArray 用于追踪表单控件数组的值和状态。
- ControlValueAccessor 用于在 Angular 的 FormControl 实例和原生 DOM 元素之间创建一个桥梁。
实现自定义表单控件的核心在于:ControlValueAccessor,组件需要实现这个接口的功能。
interface ControlValueAccessor {
writeValue(obj: any): void
registerOnChange(fn: any): void
registerOnTouched(fn: any): void
setDisabledState(isDisabled: boolean)?: void
}
- writeValue(obj: any):该方法用于将模型中的新值写入视图或 DOM 属性中。
- registerOnChange(fn: any):设置当控件接收到 change 事件后,调用的函数
- registerOnTouched(fn: any):设置当控件接收到 touched 事件后,调用的函数
- setDisabledState?(isDisabled: boolean):当控件状态变成
DISABLED
或从DISABLED
状态变化成ENABLE
状态时,会调用该函数。该函数会根据参数值,启用或禁用指定的 DOM 元素。
在组件实现ControlValueAccessor接口后,要能正常使用的话,还需要执行注册操作。
实现ngModel双向绑定
导入FormsModule
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgZorroAntdModule } from 'ng-zorro-antd';
import { City3Component } from './city3.component';
@NgModule({
imports: [CommonModule, FormsModule, NgZorroAntdModule],
declarations: [City3Component],
exports: [City3Component],
})
export class City3Module {}
组件需要注入一个Provider去实现。
/**
* template-driven forms 模板驱动表单
* 实现 ngModel
*/
const EXE_CITY3_VALUE_ACCESSOR: ExistingProvider = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => City3Component),
multi: true,
};
@Component({
selector: 'app-city3',
templateUrl: './city3.component.html',
styleUrls: ['./city3.component.less'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [EXE_CITY3_VALUE_ACCESSOR]
})
export class City3Component implements OnInit, ControlValueAccessor {
// ...
}
实现formControl
导入ReactiveFormsModule
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { NgZorroAntdModule } from 'ng-zorro-antd';
import { City3Component } from './city3.component';
@NgModule({
imports: [CommonModule, ReactiveFormsModule, NgZorroAntdModule],
declarations: [City3Component],
exports: [City3Component],
})
export class City3Module {}
实现自定义校验器
- 使用 ValueProvider 注册
/**
* 直接提供给provider使用的校验器
* @param control AbstractControl
*/
const validateSelectValue: ValidatorFn = (control: AbstractControl): ValidationErrors => {
if (control.touched) {
const reg = CITY_REGEXPS[2];
if (control.value) {
return reg.test(control.value) ? null : { invalid: true };
} else {
return null;
}
}
};
/**
* provider 默认校验使用的方式
*/
const EXE_CITY3_VALIDATOR: ValueProvider = {
provide: NG_VALIDATORS,
useValue: validateSelectValue,
multi: true,
};
@Component({
selector: 'app-city3',
templateUrl: './city3.component.html',
styleUrls: ['./city3.component.less'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [EXE_CITY3_VALIDATOR]
})
export class City3Component implements OnInit, ControlValueAccessor {
// ...
}
- 使用 ExistingProvider 注册
/**
* 自定义校验所使用的校验方式,组件要额外实现 Validator
*/
const EXE_CITY3_VALIDATOR_2: ExistingProvider = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => City3Component),
multi: true,
};
@Component({
selector: 'app-city3',
templateUrl: './city3.component.html',
styleUrls: ['./city3.component.less'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [EXE_CITY3_VALIDATOR_2]
})
export class City3Component implements OnInit, ControlValueAccessor, Validator {
// ...
/**
* 实现自定义校验,写在组件类可介入入参
* @param control AbstractControl
*/
validate(control: AbstractControl): ValidationErrors | null {
if (control.touched) {
const reg = CITY_REGEXPS[2];
if (control.value) {
return reg.test(control.value) ? null : { invalid: true };
} else {
return null;
}
}
}
}
参考:https://segmentfault.com/a/1190000009070500、https://github.com/NG-ZORRO/ng-zorro-antd/tree/master/components/select;
自定义表单控件完整代码:https://github.com/superchow/ng-login-demo/tree/master/src/app/components/city3