網站搜尋

typescript - 如何在 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

答案#1

在 GitHub 上有幾個相關的問題:

  • https://github.com/angular/angular/issues/10887
  • https://github.com/angular/angular/issues/17736

你可以做的是取得 ControlValueAccessor 控制項的 NgControl(outerControl)以及內部 NgModel 控制項的 NgControl(innerControl):

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

分叉的 Stackblitz