如何将NgControl状态传递给Angular中的子组件,实现ControlValueAccessor? [英] How to pass NgControl status to child component in Angular, implementing ControlValueAccessor?

查看:135
本文介绍了如何将NgControl状态传递给Angular中的子组件,实现ControlValueAccessor?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

提供

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

已注入NgControl

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

在这里还缺少一些东西吗?

尽管@Eliseo的答案很有解释性,但还是有一个补充...如果要同时使用外部验证器和内部验证器,则必须相应地设置父级NgControl验证器.此外,如果您想使用验证功能,则需要使用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>

解决方案

您有两个选择:

注入NgControl,为此,您需要删除提供程序并以这种方式构造构造函数

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

然后您可以装饰输入

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

或将customFormControl的类复制到输入中.

您的输入就像

<input [ngClass]="class">

如果在自定义表单控件的构造函数中导入ElementRef

constructor(private el:ElementRef){}

并创建一个函数"copyClass"

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

您可以在writeValue,Change和OnTouched中调用此函数.

我能想到的最简单的示例是在这个堆叠闪电战

注意:如果您的问题是您在组件中使用了材质角度,则技术使用了customErrorMatcher,请查看 stackoverflow

更新另一个方法是将相同的验证器设置为输入.为此,我们使用viewChild来获取输入,并且在ngAfterViewInit中等于验证器

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

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

  }

请参见另一个stackblitz

最后一次更新,如果我们想在控件内部出现自定义错误,可以使用validate函数获取控件,而不将其注入构造函数中.组件变得像

@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
  }

新的 stackblitz

Provided

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

Injected 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. 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

@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

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.

Your input is like

<input [ngClass]="class">

If in the constructor of your custom form control import the ElementRef

constructor(private el:ElementRef){}

And create a function "copyClass"

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

You can call this function in writeValue,Change and 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

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('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. 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

这篇关于如何将NgControl状态传递给Angular中的子组件,实现ControlValueAccessor?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆