简体   繁体   English

如何将NgControl状态传递给Angular中的子组件,实现ControlValueAccessor?

[英]How to pass NgControl status to child component in Angular, implementing ControlValueAccessor?

Provided 提供

{
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => TestingComponent),
  multi: true
}

Injected NgControl 注入的NgControl

constructor(@Self() @Optional() public control: NgControl) {
  this.control && (this.control.valueAccessor = this);
}

And yet something is missing here? 但是这里缺少什么吗?

在此处输入图片说明

Although @Eliseo answer is very explanatory there is still one addition... If you want to use both external validators and internal ones then parent NgControl Validators must be set accordingly. 尽管@Eliseo的答案很有解释性,但还是有一个补充...如果要同时使用外部验证器和内部验证器,则必须相应地设置父级NgControl验证器。 Furthermore you need to use ngDoCheck lifecycle hook to handle NgControl touch status if you want to use validation as me below is a final working solution 此外,如果您想使用验证,则需要使用ngDoCheck生命周期挂钩来处理NgControl触摸状态,因为以下是我的最终解决方案

@Component({
    selector: 'app-testing',
    templateUrl: 'testing.component.html'
})
export class TestingComponent implements ControlValueAccessor, DoCheck, AfterViewInit {
    @Input()
    public required: boolean;

    @ViewChild('input', { read: NgControl })
    private inputNgModel: NgModel;

    public value: number;
    public ngControlTouched: boolean;

    constructor(@Optional() @Self() public ngControl: NgControl) {
        if (this.ngControl != null) this.ngControl.valueAccessor = this;
    }

    ngDoCheck() {
        if (this.ngControlTouched !== this.ngControl.touched) {
            this.ngControlTouched = this.ngControl.touched;
        }
    }

    ngAfterViewInit() {
        // Setting ngModel validators to parent NgControl
        this.ngControl.control.setValidators(this.inputNgModel.validator);
    }

    /**
     * ControlValueAccessor Implementation
     * Methods below
     */
    writeValue(value: number): void {
        this.value = value;
        this.onChange(this.value);
    }

    onChange: (_: any) => void = (_: any) => {};

    onTouched: () => void = () => {};

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

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

// Template
<input
    #input="ngModel"
    type="text"
    class="form-control"
    [class.is-invalid]="input.invalid && (input.dirty || input.touched || ngControlTouched)"
    [(ngModel)]="value"
    (ngModelChange)="onChange(value)"
    (blur)="onTouched()"
    [required]="required"
/>

// Usage
<app-testing [(ngModel)]="propertyDetails.whatThreeWords" name="testing" required></app-testing>

You has two options: 您有两个选择:

Inject NgControl, for this you need remove the provider and make the constructor in the way 注入NgControl,为此,您需要删除提供程序并以如下方式构造构造函数

constructor(public control:NgControl){
    if (this.control != null) {
      this.control.valueAccessor = this;
    }
  }

Then you can decorate your input like 然后,您可以像

<input [ngClass]="{'ng-invalid':control.invalid,'ng-valid':control.valid...}">

Or copy the class of the customFormControl to the input. 或将customFormControl的类复制到输入。

Your input is like 您的输入就像

<input [ngClass]="class">

If in the constructor of your custom form control import the ElementRef 如果在自定义窗体控件的构造函数中导入ElementRef

constructor(private el:ElementRef){}

And create a function "copyClass" 并创建一个函数“ copyClass”

  copyClass()
  {
    setTimeout(()=>{
       this.class=this.elementRef.nativeElement.getAttribute('class')
    })
  }

You can call this function in writeValue,Change and OnTouched. 您可以在writeValue,Change和OnTouched中调用此函数。

The most simple example I can imagine is in this stackblitz 我能想象到的最简单的例子是在这个堆叠闪电战中

NOTE: If your problem is that you're using material angular in your component, the tecnique is using a customErrorMatcher, take a look to the official docs and, if you want to this answer in stackoverflow 注意:如果您的问题是您在组件中使用了材质角度,则技术使用了customErrorMatcher,请查看官方文档 ,如果您想在stackoverflow中获得此答案

UPDATE Another aproach is set the same validators to the input. 更新另一个方法是将相同的验证器设置为输入。 For this, we use viewChild to get the input and, in ngAfterViewInit equals the validators 为此,我们使用viewChild来获取输入,并且在ngAfterViewInit中等于验证器。

 @ViewChild('input',{static:false,read:NgControl}) input

 ngAfterViewInit()
  {
    if (this.control != null) {
       this.input.control.setValidators(this.control.control.validator)
    }

  }

see another stackblitz 看到另一个堆叠闪电战

at last update if we want to has a custom error inside the control, we can use the function validate to get the control and not inject in constructor. 在最后一次更新时,如果我们想在控件内出现自定义错误,可以使用validate函数来获取控件,而不是将其注入构造函数中。 The component becomes like 组件变得像

@Component({
  ...
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomFormControlComponent),
      multi: true
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => CustomFormControlComponent),
      multi: true,
    }]

})
export class CustomFormControlComponent implements ControlValueAccessor,
      Validator, AfterViewInit {
  ...
  control:any
  @ViewChild('input', { static: false, read: NgControl }) input
  constructor() {
  }
  ngAfterViewInit() {
     this.validate(null)
  }
  validate(control: AbstractControl): ValidationErrors | null{
    if (!this.control)
      this.control=control;

    if (this.control && this.input) 
       this.input.control.setValidators(this.control.validator)

    if (control.value=="qqq")
      return {error:"Inner error:The value is 1"}

    return null
  }

a new stackblitz 新的堆叠闪电战

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM