Angular: [ControlValueAccessor] 自定义表单控件

Angular: [ControlValueAccessor] 自定义表单控件

我们在实际开发中,通常会遇到各种各样的定制化功能,会遇到有些组件会与 Angular 的表单进行交互,这时候我们一般会从外部传入一个 FormGroup 对象,然后在组件的内部写相应的逻辑对 Angular 表单进行操作。如果我们只是对表单中的一个项进行定制,将整个表单对象传入显然不合适,并且组件也会显得臃肿。

<form [formGroup]="simpleForm">                                
  <other-component [form]="simpleForm"></other-component>        
</form>

那么,我们能不能像原生表单一样去使用这些自定义组件呢?目前,开源组件 ng-zorro-antd 表单组件能和原生表单一样使用 formControlName 这个属性,这类组件就叫自定义表单组件。

如何实现自定义表单控件

在 Angular 中,使用 ControlValueAccessor 可以实现组件与外层包裹的 form 关联起来。

ControlValueAccessor

ControlValueAccessor 接口定义了四个方法:

writeValue(obj: any): void

registerOnChange(fn: any): void

registerOnTouched(fn: any): void

setDisabledState(isDisabled: boolean)?: void

writeValue:任何 FormControl 显式调用 API 的值操作都将调用自定义表单组件的 writeValue() 方法,它将来自外部的数据写入到内部的数据模型。数据流向: form model -> component。

registerOnChange:注册 onChange 事件, form 初始化时被调用,参数为事件触发函数。通常在 registerOnChange 中需要保存该事件触发函数,在数据改变的时候,可以通过调用事件触发函数通知外部数据变了,同时可以将修改后的数据作为参数传递出去。onChange 事件函数的参数为 form model 要接收的 value。数据流向: component -> form model。

registerOnTouched:注册 onTouched 事件,基本同 registerOnChange ,只是该函数用于通知表单组件已经处于 touched 状态,改变绑定的 FormControl 的内部状态。状态变更: component -> form model。

setDisabledState:当调用 FormControl 变更状态的 API 时得表单状态变为 Disabled 时调用 setDisabledState() 方法,以通知自定义表单组件当前表单的读写状态。状态变更: form model -> component。

如何使用 ControlValueAccessor

搭建控件框架

@Component({
  selector: 'app-custom-input',
  // 这里将组件注册为 Accessor
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true,
    },
     {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true,
    }
  ],
  templateUrl: './custom-input.component.html'
})
export class CustomInputComponent implements ControlValueAccessor {

  innerValue: any;

  disabled: boolean;

  private _onChange = (_: any) => {}; 
  private _onTouched = () => {};

  constructor() {}

  valueChange(value: any) {
    // 触发 onChange,component 内部的值同步到 form model
    this.onChange(value);
  }

  // form model 的值同步到 component 内部
  writeValue(obj: any): void {
    this.innerValue = obj;
  }

  // 保存 onChange 事件函数
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  // 保存 onTouched 事件函数
  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  // form model 的状态同步到 component 内部
  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

}

除了这些函数,还应注意到,我们注册两个 provider,一个的 tokenNG_VALUE_ACCESSOR 这是将控件本身注册到 DI 框架成为一个可以让表单访问其值的控件。但问题来了,如果在元数据中注册了控件本身,而此时控件仍为创建,这怎么解决?这就得用到 forwardRef 了,这个函数允许我们引用一个尚未定义的对象。另外一个 NG_VALIDATORS 是让控件注册成为一个可以让表单得到其验证状态的控件。这里的 multi: true,,是声明这个 token 对应的类很多,分散在各处。

控件界面

<input [(ngModel)]="innerValue" (ngModelChange)="valueChange($event)" />

在表单中使用

<form nz-form [formGroup]="baseForm">
    <app-custom-input formControlName="name" [(ngModel)]="name">
    </app-custom-input>
</form>

参考

发布了89 篇原创文章 · 获赞 79 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/oschina_41790905/article/details/101716227
今日推荐