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

时间:2018-02-08 09:50:02

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

我正在尝试通过实现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。

4 个答案:

答案 0 :(得分: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;
    }
  }
}

答案 1 :(得分:0)

一个干净的方法是创建一个np.newaxis,其选择器与@Directive相同。这样,@Component可以镜像@Directive拥有的任何@Input并在验证时对其进行说明。

@Component

答案 2 :(得分:0)

我的解决方案采用了@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 (this.phoneNumberInputComponent?.myInput) {
      // some custom logic
    }
    return null;
  }
}

答案 3 :(得分:0)

我不确定您是否需要实现验证器接口。对于我的自定义控件,我使用注入的 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
/>