【问题标题】:Angular ControlValueAccessor and markAsTouchedAngular ControlValueAccessor 和 markAsTouched
【发布时间】:2020-08-17 09:02:10
【问题描述】:

我将 Angular 9 与 Angular Material 一起使用,并且通过实现 ControlValueAccessor 接口来自定义控件。一切正常。

当表单无效时,在我的所有提交按钮中,我调用 formGroup.markAllAsTouched 因为所有角度材料字段都变为红色。这样用户可以更好地了解哪些控件是无效的。

我需要用我的自定义控件实现相同的行为。该怎么做?

为了更好地了解情况,我创建了一个 stackblitz 项目here

【问题讨论】:

  • 您的控件没有标记为已触摸?你在你的控件中实现了 registerOnTouched 吗?你能分享你的代码吗?
  • 我正在准备一个 stackblitz 示例。我会尽快回复你
  • 首先,我不知道你为什么在另一个 ControlValueAccesor 组件中包装一个 mat-select,你可以将 userTypeCustomControl 共享给自定义控件并在 mat-select 中使用它。你对类和表单控件引用有问题

标签: angular angular-material angular-forms


【解决方案1】:

no built-in functionality 用于将touched 状态传播到自定义控件的内部FormControl。

您的简单选择是检查ngDoCheck 中的状态,一旦触摸到自定义控件,就可以更新内部FormControl 的状态:

ngDoCheck() {
  if (this.formControl.touched) {
    return;
  }
  if (this.controlDir.control.touched) {
    this.formControl.markAsTouched();
  }
}

Forked Stackblitz

就我个人而言,我不喜欢ControlValueAccessor 这样的实现。 我宁愿使用相同的 FormControl。这可以通过将 viewProvidersControlValueAccessor 提供程序添加到您的自定义控件来完成:

custom-control.component.ts

@Component({
  selector: 'my-custom-control',
  template: `
    <mat-form-field id="userType">
      <mat-label>My Custom Component</mat-label>
      <mat-select [formControlName]="controlName" (blur)="onTouched()">
        <mat-option *ngFor="let current of userTypes" [value]="current.id">{{current.name}}</mat-option>
      </mat-select>
    </mat-form-field>

  `,
   viewProviders: [{
    provide: ControlContainer,
    useFactory: (container: ControlContainer) => container,
    deps: [[new SkipSelf(), ControlContainer]],
 }]
})
export class MyCustomControl {
  @Input() controlName: string;

  userTypes: LookupModel[] = [
      new LookupModel(1, 'first'),
      new LookupModel(2, 'second')
  ];
}

父 html

<form [formGroup]="form">
  <my-custom-control controlName="userTypeCustomControl"></my-custom-control>

Stackblitz Example

【讨论】:

  • ControlValueAccessor 模式是否存在问题,因此您选择了第二种解决方案,还是只是个人喜好?
【解决方案2】:

另一个机会是使用以下方法

自定义控制代码

@Component({
  selector: 'cvl-advertising-type',
  templateUrl: './advertising-type.component.html',
  styleUrls: ['./advertising-type.component.scss'],
})
export class AdvertisingTypeComponent implements OnInit, ControlValueAccessor {
  advertisingTypes: ReadonlyArray<LookupModel>;

  onChange = (value: number) => {
  };
  onTouched = () => {
  };

  constructor(private lookupService: LookupService,
              @Self() public controlDir: NgControl) {
    controlDir.valueAccessor = this;
  }

  ngOnInit(): void {
    this.advertisingTypes = this.lookupService.advertisingTypes;
  }

  registerOnChange(fn: (value: number) => void): void {
    this.controlDir.control.valueChanges.subscribe(fn);
    this.onChange = fn;
  }

  writeValue(value: number): void {
    this.controlDir.control.setValue(value);
    this.onChange(value);
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    if (isDisabled) {
      this.controlDir.control.disable();
    } else {
      this.controlDir.control.enable();
    }
  }
}

和父 html 表单

<cvl-advertising-type [formControl]="form.controls.advertisingType"></cvl-advertising-type>

查看video后我找到了这个解决方案

最后但并非最不重要的问题是我不明白如何用浅层测试来测试它,因为我有以下错误

Error: NodeInjector: NOT_FOUND [NgControl]

【讨论】:

  • 为了正确测试它,您可以使用包装组件或为NgControl添加@Optional装饰器
猜你喜欢
  • 2019-03-27
  • 2020-03-24
  • 1970-01-01
  • 2017-08-26
  • 2017-03-19
  • 2021-09-29
  • 2020-07-07
  • 2019-01-05
  • 2019-11-02
相关资源
最近更新 更多