实现 ControlValueAccessor 和 Validator 的 MatFormFieldControl 创建循环依赖

Posted

技术标签:

【中文标题】实现 ControlValueAccessor 和 Validator 的 MatFormFieldControl 创建循环依赖【英文标题】:MatFormFieldControl that implements ControlValueAccessor and Validator creates cyclic dependency 【发布时间】:2018-07-18 20:05:37 【问题描述】:

我正在尝试通过实现 MatFormFieldControl、ControlValueAccessor 和 Validator 接口来创建自定义表单控件。

但是,当我提供 NG_VALUE_ACCESSORNG_VALIDATORS..

@Component(
  selector: 'fe-phone-number-input',
  templateUrl: './phone-number-input.component.html',
  styleUrls: ['./phone-number-input.component.scss'],
  providers: [
    
      provide: MatFormFieldControl,
      useExisting: forwardRef(() => PhoneNumberInputComponent)
    ,
    
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => PhoneNumberInputComponent),
      multi: true,
    ,
    
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => PhoneNumberInputComponent),
      multi: true
    
  ]
)
export class PhoneNumberInputComponent implements MatFormFieldControl<string>,
  ControlValueAccessor, Validator, OnDestroy 
  ...

循环依赖被创建:

未捕获的错误:模板解析错误: 无法实例化循环依赖! NgControl

这行得通:

@Component(
  selector: 'fe-phone-number-input',
  templateUrl: './phone-number-input.component.html',
  styleUrls: ['./phone-number-input.component.scss'],
  providers: [
    
      provide: MatFormFieldControl,
      useExisting: forwardRef(() => PhoneNumberInputComponent)
    
  ]
)
export class PhoneNumberInputComponent implements MatFormFieldControl<string>,
  ControlValueAccessor, Validator, OnDestroy 
  ...
  constructor(@Optional() @Self() public ngControl: NgControl) 
    if (this.ngControl) 
      this.ngControl.valueAccessor = this;
    
  

但我仍然无法弄清楚如何进行验证。提供NG_VALIDATORS 会产生循环依赖。如果不提供,validate 方法根本不会被调用。

我正在使用@angular/material 5.0.4。

【问题讨论】:

【参考方案1】:

为了摆脱循环依赖,我从组件中移除了Validator接口,而是直接提供了验证器函数。

export function phoneNumberValidator(control: AbstractControl) 
  ...


@Component(
  selector: 'fe-phone-number-input',
  templateUrl: './phone-number-input.component.html',
  styleUrls: ['./phone-number-input.component.scss'],
  providers: [
    
      provide: MatFormFieldControl,
      useExisting: forwardRef(() => PhoneNumberInputComponent)
    ,
    
      provide: NG_VALIDATORS,
      useValue: phoneNumberValidator,
      multi: true
    
  ]
)
export class PhoneNumberInputComponent implements MatFormFieldControl<string>,
  ControlValueAccessor, OnDestroy 
  ...
  constructor(@Optional() @Self() public ngControl: NgControl) 
    if (this.ngControl) 
      this.ngControl.valueAccessor = this;
    
  

【讨论】:

您将如何从验证器函数访问输入组件实例?有可能吗? @MaxMumford 在下面看到我的回答【参考方案2】:

我的解决方案借鉴了@blid 的想法,而是复制与正在验证的组件相同的@Inputs,我通过依赖注入注入组件,如下所示:

@Directive(
  selector: 'fe-phone-number-input, [fePhoneNumber]',
  providers: [
    
      provide: NG_VALIDATORS,
      useExisting: PhoneNumberInputValidatorDirective,
      multi: true
    
  ]
)
export class PhoneNumberInputValidatorDirective implements Validator 
  constructor(private injector: Injector) 
  
  validate(control: FormControl) 
    // use @Self to get the only instance of component that this validator is directly attached to
    // use @Optional so that this validator can be used separately as a directive via attribute `fePhoneNumber`
    const phoneNumberInputComponent = this.injector.get(PhoneNumberInputComponent, undefined, InjectFlags.Self | InjectFlags.Optional);

    if (phoneNumberInputComponent?.myInput) 
      // some custom logic
    
    return null;
  

【讨论】:

【参考方案3】:

一个干净的做法是创建一个@Directive,其选择器与@Component 相同。这种方式使@Directive 能够镜像@Component 拥有的任何@Input,并在验证时对其进行说明。

@Directive(
  selector: 'fe-phone-number-input',
  providers: [
    
      provide: NG_VALIDATORS,
      useExisting: PhoneNumberInputValidatorDirective,
      multi: true
    
  ]
)
export class PhoneNumberInputValidatorDirective implements Validator  ... 

【讨论】:

【参考方案4】:

我不确定您是否需要实现 validator 接口。对于我的自定义控件,我使用注入的 ngControl 进行任何验证。

@Component(
  selector: 'fe-phone-number-input',
  templateUrl: './phone-number-input.component.html',
  styleUrls: ['./phone-number-input.component.scss'],
  providers: [
    
      provide: MatFormFieldControl,
      useExisting: forwardRef(() => PhoneNumberInputComponent),
    ,
  ],
)
export class PhoneNumberInputComponent
  implements MatFormFieldControl<string>, ControlValueAccessor


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

  updatePhone(phone: string) 
    this.value = phone;
    this.ngControl.control?.updateValueAndValidity();
   

<input
  class="phone-number-input mat-input-element"
  type="text"
  (blur)="updateValue(phoneInput.value)"
  [required]="required"
  #phoneInput
/>

【讨论】:

以上是关于实现 ControlValueAccessor 和 Validator 的 MatFormFieldControl 创建循环依赖的主要内容,如果未能解决你的问题,请参考以下文章

嵌套 ControlValueAccessor 的角度验证状态未在父级中正确传播,如何实现此验证?

带有 ControlValueAccessor 测试的 Angular 2(4) 组件

为什么更改事件是针对controlValueAccessor的模糊触发的

带有 ControlValueAccessor 和 formControlName 的 Angular Material Datepicker [重复]

为啥在 Angular 中实现 ControlValueAccessor 时需要在 writeValue 中调用 onChange 和 onTouch?

Angular 材质中具有错误验证的 ControlValueAccessor