在Angular中使用ControlValueAccessor继承验证

40
我有一个自定义表单控件组件(它是一个高级输入框)。之所以使用自定义组件,是为了方便UI更改 - 即如果我们从根本上更改样式输入控件的方式,很容易在整个应用程序中传播这种更改。
目前,我们在Angular中使用Material Design https://material.angular.io,当控件无效时,它会非常好地设置控件样式。
我们已经实现了ControlValueAccessor,以便允许我们将formControlName传递给我们的自定义组件,这个功能完美地工作;当自定义控件有效/无效时,表单也是有效/无效的,应用程序按预期运行。
然而,问题在于我们需要根据是否有效来设置自定义组件内部的UI样式,但我们似乎做不到 - 实际需要设置样式的输入框从未得到验证,它只是将数据传递给父组件。
COMPONENT.ts
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
    AbstractControl,
    ControlValueAccessor,
    NG_VALIDATORS,
    NG_VALUE_ACCESSOR,
    ValidationErrors,
    Validator,
} from '@angular/forms';

@Component({
  selector: 'app-input',
  templateUrl: './input.component.html',
  styleUrls: ['./input.component.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => InputComponent),
      multi: true
    }
  ]
})
export class InputComponent implements OnInit, ControlValueAccessor {
  writeValue(obj: any): void {
    this._value = obj;
  }
  registerOnChange(fn: any): void {
    this.onChanged = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  get value() {
    return this._value;
  }

  set value(value: any) {
    if (this._value !== value) {
      this._value = value;
      this.onChanged(value);
    }
  }

  @Input() type: string;

  onBlur() {
    this.onTouched();
  }

  private onTouched = () => {};
  private onChanged = (_: any) => {};
  disabled: boolean;

  private _value: any;

  constructor() { }

  ngOnInit() {
  }

}

COMPONENT.html

<ng-container [ngSwitch]="type">
  <md-input-container class="full-width" *ngSwitchCase="'text'">
    <span mdPrefix><md-icon>lock_outline</md-icon> &nbsp; </span>
    <input mdInput placeholder="Password" type="text" [(ngModel)]="value" (blur)="onBlur()" />
  </md-input-container>
</ng-container>

在页面上的使用示例:

HTML:

<app-input type="text" formControlName="foo"></app-input>

TS:

this.form = this.fb.group({
        foo: [null, Validators.required]
    });

也许这就是你一直在寻找的东西?https://stackoverflow.com/questions/48573931/angular-5-glue-logic-of-components-dynamically-added-to-form - Guillermo Prandi
3个回答

39

您可以通过 DI 获取 NgControl 的访问权限。 NgControl 包含有关验证状态的所有信息。要检索 NgControl,您不应该通过 NG_VALUE_ACCESSOR 提供组件,而应该在构造函数中设置访问器。

@Component({
  selector: 'custom-form-comp',
  templateUrl: '..',
  styleUrls: ...
})
export class CustomComponent implements ControlValueAccessor {

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

   // ControlValueAccessor methods and others

   public get invalid(): boolean {
     return this.control ? this.control.invalid : false;
   }

   public get showError(): boolean {
      if (!this.control) {
       return false;
      }

      const { dirty, touched } = this.control;

      return this.invalid ? (dirty || touched) : false;
   }
}
请阅读这篇文章,以了解完整信息。

2
那么我们通过注入的NgControl来检索formControlName="foo"是否有效,对吗?我们可以在custom-form-comp的模板中使用get invalid()布尔变量来设置其内部元素的样式,对吗?是否有一种方法(并且是否有意义)将类ng-touched、ng-dirty、ng-invalid等从<custom-form-comp>组件转发到其内部元素? - user2010955
4
对于 Angular 8,我可以简化构造函数为 constructor(private control: NgControl) { this.control.valueAccessor = this; } - mariszo
1
嗨@VJAI,我正在尝试采用您的答案,并将其应用于这个有些不同的问题,您是否有更优化的答案?https://dev59.com/yFIH5IYBdhLWcg3wkv30 谢谢 - user12425844
6
这非常有帮助,虽然我需要重新阅读几次才注意到我需要停止通过 NG_VALUE_ACCESSOR 提供,并改用 .valueAccessor。谢谢! - Coderer
2
@Coderer,感谢您的评论,我成功地让它工作了。我一直在收到循环依赖错误的提示,没有注意到答案中省略了providers数组。太棒了! - Божидар Йовчев
1
虽然现在这个方法可以工作,但我面临着相反的问题,即无法从内部验证输入,因为提供NG_VALIDATORS也会导致循环依赖。是否有可能既能进行内部验证(例如针对正则表达式),又能进行外部验证(在使用该组件的表单上使用Validators.required)? - Yasammez

5

我遇到了完全相同的问题。在获取控制器后,您是如何将验证反映到输入中的? - Erex
指向正确方向的建议不错,但确实,您是如何做到的呢? - SomeOne_1
感谢您指引我正确的方向,那个页面上真正对我有用的答案是这个 - Alberto Rechy

-1
此外:可能被视为不太规范,但对我而言起到了作用:
  1. 让您的组件实现 Validator 接口。 2 在 validate 函数中,您可以使用 controlcontainer 来获取组件外部 formcontrol。
  2. 通过使用变量跟踪父表单控件的状态(VALID / INVALID)。
  3. 检查是否已触摸。只有在 touched 为 true 且状态已更改时,才对字段执行验证操作。

4
可以举一个例子,特别是 validate 方法的例子吗? - Cody
5
这个回答需要更多的细节。考虑发布一个StackBlitz示例。 - Andre Elrico
1
似乎是在引用这篇文章:https://lukaonik.medium.com/adding-custom-validation-to-the-custom-form-control-in-angular-f4e9b13b4728 - Vedran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接