【问题标题】:MatFormFieldControl that implements ControlValueAccessor and Validator creates cyclic dependency实现 ControlValueAccessor 和 Validator 的 MatFormFieldControl 创建循环依赖
【发布时间】: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。

【问题讨论】:

    标签: angular typescript angular-material angular5 angular-components


    【解决方案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
        />
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-06-02
          • 2018-06-02
          • 1970-01-01
          • 1970-01-01
          • 2020-11-18
          • 1970-01-01
          • 1970-01-01
          • 2015-05-27
          相关资源
          最近更新 更多