实现 ControlValueAccessor 和 Validator 的 MatFormFieldControl 创建循环依赖
MatFormFieldControl that implements ControlValueAccessor and Validator creates cyclic dependency
我正在尝试通过实现 MatFormFieldControl、ControlValueAccessor 和 Validator 接口来创建自定义表单控件。
但是,当我提供 NG_VALUE_ACCESSOR
或 NG_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 {
...
}
创建了循环依赖:
Uncaught Error: Template parse errors:
Cannot instantiate cyclic dependency! 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.
为了摆脱循环依赖,我从组件中删除了 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;
}
}
}
一个干净的做法是使用与 @Component
相同的选择器创建一个 @Directive
。这种方式使 @Directive
能够镜像 @Component
拥有的任何 @Input
并在验证时考虑到它。
@Directive({
selector: 'fe-phone-number-input',
providers: [
{
provide: NG_VALIDATORS,
useExisting: PhoneNumberInputValidatorDirective,
multi: true
}
]
})
export class PhoneNumberInputValidatorDirective implements Validator { ... }
我的解决方案采用了@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;
}
}
我不确定您是否需要实现 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
/>
我正在尝试通过实现 MatFormFieldControl、ControlValueAccessor 和 Validator 接口来创建自定义表单控件。
但是,当我提供 NG_VALUE_ACCESSOR
或 NG_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 {
...
}
创建了循环依赖:
Uncaught Error: Template parse errors: Cannot instantiate cyclic dependency! 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.
为了摆脱循环依赖,我从组件中删除了 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;
}
}
}
一个干净的做法是使用与 @Component
相同的选择器创建一个 @Directive
。这种方式使 @Directive
能够镜像 @Component
拥有的任何 @Input
并在验证时考虑到它。
@Directive({
selector: 'fe-phone-number-input',
providers: [
{
provide: NG_VALIDATORS,
useExisting: PhoneNumberInputValidatorDirective,
multi: true
}
]
})
export class PhoneNumberInputValidatorDirective implements Validator { ... }
我的解决方案采用了@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;
}
}
我不确定您是否需要实现 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
/>