将 FormControl 验证器绑定到自定义表单 Material Select 组件 [英] Binding FormControl validators to a custom form Material Select component

查看:24
本文介绍了将 FormControl 验证器绑定到自定义表单 Material Select 组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我通过示例设置了这个

解决方案

非常感谢@Eliseo,但该解决方案对我现有的代码不起作用(不同的绑定方式,Angular 8?),我得到了更多沮丧 - ngControl.control 总是未定义..

该解决方案显然不需要自定义 ErrorStateMatcher,但答案是确保 mat-select 绑定到 FormControlFormGroup 由于生命周期事件而繁琐,但有效:

export class DatasetSelectComponent extends AbstractFormFieldComponent {@Input() 标签!:字符串;@Input() items!: [{id: number, label: string}];}导出抽象类 AbstractFormFieldComponent 实现 ControlValueAccessor {//tslint:disable-next-line:variable-name_formControl = new FormControl();onChange = (value: any) =>{};构造函数(@Self()@Optional()公共ngControl:NgControl){如果(this.ngControl){this.ngControl.valueAccessor = this;}}ngAfterViewInit(): 无效 {如果(this.ngControl){/*** 获取在组件注入层次结构中最后一个 Reactive FormGroup 中创建的 FormControl 的句柄* 以便它可以绑定到我们自定义组件中的输入* 这确保输入值绑定到模型 + 显式验证绑定* 例如new FormGroup({ titleId: new FormControl(personalDetails.titleId, Validators.required) } =>*  !fc),拿(1)).subscribe(fc => {this.formControl = fc as FormControl;控制台.日志('自定义 FormControl (AbstractFormFieldComponent): 绑定到响应式表单',this.ngControl,this.ngControl.control);});}get formControl() :FormControl|RequiredFormControl {返回 this._formControl;}设置 formControl(forControl:FormControl|RequiredFormControl) {this._formControl = forControl;}registerOnChange(fn: (value: any) => void): void {this.onChange = fn;}registerOnTouched(fn: (value: any) => void): void {}writeValue(value: any): void {if(this.formControl) this.formControl.setValue(value, { emitEvent: false });}}

注意删除了 NG_VALUE_ACCESSOR 的组件注入(由构造函数中的工作代替),这可以防止循环依赖编译时错误:

提供者:[{提供:NG_VALUE_ACCESSOR,多:真实,useExisting: forwardRef(() => CustomSelectComponent),}]

以及模板中的一个片段:

 <mat-option *ngFor="let item of items" [value]="item.id">{{ item.label }}</mat-option></mat-select>

I have this stackblitz set up by way of an example.

I have a standard input form field and a custom field that shows a select bound to an array.

<form [formGroup]="formGroup">
    <mat-form-field class="field">
      <mat-label>City</mat-label>
      <input matInput placeholder="City" formControlName="address1" />
    </mat-form-field>

    <app-dataset-select label="Country" [items]="countries" formControlName="countryId"></app-dataset-select>

</form>

The whole thing is wrapped by a form with validation:

 this.formGroup = new FormGroup({
    address1: new FormControl(model.address1, Validators.required),
    countryId: new FormControl(model.countryId, Validators.required)
  });

When I click SAVE I expect both fields to visibly show validation - the FormGroup itself says we do.

But the Country control does not get the ng-invalid state (and thus no redness) and I'm not sure why - although its something to do with angular's reactive forms kung-fu black magic...

解决方案

Big thanks to @Eliseo but that solution was not working for me on my existing code (different way of binding, Angular 8?) and I was getting even more frustrated - ngControl.control was always undefined..

The solution does not require a custom ErrorStateMatcher apparently, but the answer is to ensure the mat-select is bound to the FormControl in the FormGroup which is fiddly due to life-cycle events, but effectively:

export class DatasetSelectComponent extends AbstractFormFieldComponent {
  @Input() label!: string;
  @Input() items!: [{id: number, label: string}];
}

export abstract class AbstractFormFieldComponent implements  ControlValueAccessor {

  // tslint:disable-next-line:variable-name
  _formControl = new FormControl(); 
  onChange = (value: any) => {};

 constructor(@Self() @Optional() public ngControl: NgControl) {
    if(this.ngControl) {
      this.ngControl.valueAccessor = this;
    }
  }

  ngAfterViewInit(): void {
    if (this.ngControl) {
      /**
       * get a handle on the FormControl that was created in the last Reactive FormGroup in the component injection hierarchy
       * so that it can be bound to the input in our Custom Component
       * this ensures input value binding to model + explicit validation is bound
       * e.g. new FormGroup({ titleId: new FormControl(personalDetails.titleId, Validators.required) } =>
       *    <input [formControl]="this.formControl"
       * otherwise you will have to do that manually for evey single control on every single form
       * which is obviously a lot of repeating yourself
       */

      of(this.ngControl.control)
        .pipe(
          skipWhile(fc => !fc),
          take(1)
        )
        .subscribe(fc => {
          this.formControl = fc as FormControl;
          console.log(
            'Custom FormControl (AbstractFormFieldComponent): Binding to Reactive Form',
            this.ngControl,
            this.ngControl.control
          );
        });
    }

  get formControl() :FormControl|RequiredFormControl {
    return this._formControl;
  }
  set formControl(forControl:FormControl|RequiredFormControl)  {
    this._formControl = forControl;
  }

  registerOnChange(fn: (value: any) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: (value: any) => void): void {}

  writeValue(value: any): void {
    if(this.formControl) this.formControl.setValue(value, { emitEvent: false });
  }


}

Note the removal of the component injection of NG_VALUE_ACCESSOR (replaced by the workings in the constructor), which prevents a cyclical dependency compile-time error:

providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: forwardRef(() => CustomSelectComponent),
    }
  ]

And a snippet from the template:

  <mat-select [formControl]="formControl" [required]="formControl.required">
    <mat-option *ngFor="let item of items" [value]="item.id">
      {{ item.label }}
    </mat-option>
  </mat-select>

Updated blitz

这篇关于将 FormControl 验证器绑定到自定义表单 Material Select 组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆