【问题标题】:How to pass NgControl status to child component in Angular, implementing ControlValueAccessor?如何将 NgControl 状态传递给 Angular 中的子组件,实现 ControlValueAccessor?
【发布时间】:2019-12-02 19:47:12
【问题描述】:

提供

{
  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>

【问题讨论】:

  • 有什么问题?
  • 子组件必须通过父组件 NgForm 进行验证 (form.controls[key].markAsTouched();)。但是验证设置在一个孩子身上。感染 NgControl 不允许我将以下内容应用于孩子的输入 [class.is-invalid]="control.invalid && (control.dirty || control.touched)"
  • 可以分享自定义控件类的代码吗?

标签: angular


【解决方案1】:

你有两个选择:

注入 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中调用这个函数。

我能想象到的最简单的例子是this stackblitz

注意:如果您的问题是您在组件中使用了材料角度,则该技术正在使用 customErrorMatcher,请查看official docs,如果您想在stackoverflow 中获得此答案

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

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

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

  }

another 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

【讨论】:

  • 我今天早上刚刚查看了我的代码,发现我还没有实现模糊事件......但我目前正在寻找一种方法来实现自定义验证器(必需、模式和自定义)必须存在)在子组件上并传输验证状态并将验证状态传输到父表单
  • 验证器可以在组件之外——那么你不需要在组件内部做任何事情,见stackoverflow.com/questions/53793764/…
  • 是的,但在我的情况下,我需要它们,只有 required 可以通过输入装饰器有条件地设置。提供 NG_VALIDATORS,设置验证,但它不影响子控件验证状态...父是 ng-invalid,而子输入是 ng-valid
  • 在里面做一个验证器没有多大意义——只有当是一个自定义验证器时——。无论如何,我刚刚发现了一种更简单的方法来让我们的“输入”得到错误并复制验证器。 -查看我更新的答案-如果我们内部有自定义验证器,我们需要使用 setvalidator,例如 setValidators([..this.control.control.validator,newValidator])
  • 谢谢@Eliseo。我花了很多时间来获得 NgControl 触摸状态。查看我的更新。你的回答对我很有帮助!
猜你喜欢
  • 2019-08-15
  • 1970-01-01
  • 2020-07-04
  • 2019-01-20
  • 2022-01-18
  • 2017-05-31
  • 2014-12-18
  • 2020-05-18
  • 2020-06-21
相关资源
最近更新 更多