Angular ControlValueAccessor 和 markAsTouched

Angular ControlValueAccessor and markAsTouched

我正在使用 angular 9 和 Angular Material,并且我通过实现 ControlValueAccessor 接口有一个自定义控件。一切正常。

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

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

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

用于将 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')
  ];
}

parent html

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

Stackblitz Example

另一个机会是使用下面的方法

自定义控件代码

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

和parenthtml形式

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

我在查看此 video

后找到了这个解决方案

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

Error: NodeInjector: NOT_FOUND [NgControl]