如何在Angular的自定义控件组件中触摸内部控件?

问题描述

我有一个带有自己的自定义控件组件的表单:

@Component({
  selector: "my-app",template: `
    <form [formGroup]="form">
      <app-custom-control formControlName="customControl"></app-custom-control>
    </form>

    <button (click)="touch()">
      Touch!
    </button>
  `,styleUrls: ["./app.component.css"]
})
export class AppComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.form = this.fb.group({
      customControl: "1"
    });
  }

  touch() {
    this.form.markAllAsTouched();
  }
}

我的自定义控件组件内部又有一个自定义控件组件(在我的真实应用中,这是必需的,因为外部控件组件具有两种模式-读取和编辑):

@Component({
  selector: "app-custom-control",template: `
    <ng-select [ngModel]="value" [items]="items"></ng-select>
  `,styleUrls: ["./custom-control.component.css"],providers: [
    {
      provide: NG_VALUE_ACCESSOR,useExisting: CustomControlComponent,multi: true
    }
  ]
})
export class CustomControlComponent implements ControlValueAccessor,OnInit {
  items = ["1","2"];

  value = null;
  onChange = (value: any) => {};
  onTouched = () => {};

  constructor() {}

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

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

  writeValue(outsideValue: number) {
    this.value = outsideValue;
  }
}

可以将NG_VALIDATORS添加CustomControlComponent并实现应如何验证组件的方式(它可以产生ng-select错误)。但是我真的不知道在包装表格的组件中进行this.form.markAllAsTouched()时如何触摸内部组件(ng-select)。

我尝试在ngDoCheck生命周期方法中进行操作,但似乎运行了太多次,这是不可接受的解决方案。

游乐场:https://stackblitz.com/edit/angular-ivy-u3kdfj

解决方法

在github中有几个相关的问题:

您可以做的是为ControlValueAccessor控件(outerControl)和内部NgModel控件(innerControl)获取NgControl:

custom-control.component.ts

export class CustomControlComponent implements ControlValueAccessor,AfterViewInit {
  @ViewChild(NgControl) innerNgControl: NgControl;

  constructor(private inj: Injector) {}

  ...

  ngAfterViewInit() { 
    const outerControl = this.inj.get(NgControl).control;
    ...
  }
}

然后,您可以使用此技巧将触摸状态绕过内部控制:

const prevMarkAsTouched = outerControl.markAsTouched;
outerControl.markAsTouched = (...args: any) => { 
  this.innerNgControl.control.markAsTouched();
  prevMarkAsTouched.bind(outerControl)(...args);
};

Forked Stackblitz