【问题标题】:Binding FormControl validators to a custom form Material Select component将 FormControl 验证器绑定到自定义表单 Material Select 组件
【发布时间】:2020-05-20 13:54:32
【问题描述】:

我有这个stackblitz 作为示例设置。

我有一个标准的input 表单字段和一个自定义字段,显示绑定到数组的select

<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>

整个事情都被一个带有验证的表单包裹起来:

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

当我点击 SAVE 时,我希望这两个字段都能明显地显示验证 - FormGroup 本身就说明了。

但是 Country 控件没有获得 ng-invalid 状态(因此没有发红),我不知道为什么 - 尽管它与 angular 的有关反应形式功夫黑魔法......

【问题讨论】:

  • 哎呀。这是如此晦涩的角度!你不会相信我打了多少次谷歌却找不到这些:(
  • 问题在于自定义控件有错误(您可以看到 Angular 为自定义控件提供了 ng-valid 和 ng-invalid 类),而不是“内部”mat-input .就是这样
  • 以及如何在 mat-select 上设置必需的属性以获得星号?我仍然遇到问题...在您的示例中,我没有看到(使用开发工具)文本设置为红色。
  • 查看我对星号的回答

标签: angular angular-material angular-forms


【解决方案1】:

好吧,如果我们在需要时想要一个“星号”,一种方法是我们的​​ mat-input 将 [required] 属性添加到我们的内部输入

<mat-select ... [required]="isRequired?true:null">

如何给 isRequired 变量赋值?

好吧,我喜欢在构造函数中使用并询问是否有属性(*)

  constructor(@Attribute('required') required, public injector: Injector) {
    this.isRequired=required!=undefined
  }

我们使用我们的自定义组件,如

<app-custom-select placeholder="My State" formControlName="state" 
     [optionList]="stateList" required>
</app-custom-select>

在这种情况下,我们不能包含 Validators.required

如果需要,forked stackblitz 包含 *

(*) 我们也可以使用简单的@Input,但只有在我们想要动态更改值时才必须使用@Input

【讨论】:

  • 是不错的选择 - 我已将 FormControl 扩展为另一种选择
【解决方案2】:

非常感谢@Eliseo,但该解决方案不适用于我现有的代码(不同的绑定方式,Angular 8?),我变得更加沮丧 - ngControl.control 始终未定义..

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

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 });
  }


}

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

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

还有来自模板的 sn-p:

  <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

【讨论】:

  • ¡¡太棒了!!确定我前段时间正在努力避免 errorMatcher 并且没有找到密钥
  • 我使用了上面更新的 Stackblitz 中的代码。自定义控件有效,但是当 FormGroup 被重置时,它会触发 RangeError: Maximum call stack size exceeded
猜你喜欢
  • 1970-01-01
  • 2021-04-29
  • 1970-01-01
  • 2020-05-29
  • 2020-01-08
  • 1970-01-01
  • 1970-01-01
  • 2019-07-05
  • 2020-05-28
相关资源
最近更新 更多