将 FormControl 验证器绑定到自定义表单 Material Select 组件
Binding FormControl validators to a custom form Material Select component
我通过示例设置了这个 stackblitz。
我有一个标准的 input
表单字段和一个显示 select
绑定到数组的自定义字段。
<form [formGroup]="formGroup">
<mat-form-field class="field">
<mat-label>City</mat-label>
<input matInput placeholder="City" formControlName="address1" />
</mat-form-field>
<app-dataset-select label="Country" [items]="countries" formControlName="countryId"></app-dataset-select>
</form>
整个事情都被一个带有验证的表单包装起来:
this.formGroup = new FormGroup({
address1: new FormControl(model.address1, Validators.required),
countryId: new FormControl(model.countryId, Validators.required)
});
当我点击 SAVE 时,我希望两个字段都能明显显示验证 - FormGroup
本身说我们这样做。
但是 Country 控件没有得到 ng-invalid 状态(因此没有发红),我不确定为什么- 虽然它与 angular 的反应形式功夫黑魔法有关...
好吧,如果我们在需要时想要 "asterisk",一种方法是将我们的 mat-input 添加 [required] 属性到我们的内部输入
<mat-select ... [required]="isRequired?true:null">
如何给isRequired变量赋值?
好吧,我喜欢在构造函数中使用并询问是否有属性(*)
constructor(@Attribute('required') required, public injector: Injector) {
this.isRequired=required!=undefined
}
我们使用自定义组件,例如
<app-custom-select placeholder="My State" formControlName="state"
[optionList]="stateList" required>
</app-custom-select>
在那种情况下我们不能包含 Validators.required
A forked stackblitz 包含 *(如果需要)
(*) 我们也可以使用简单的@Input,但是只有当我们想要动态更改值时才必须使用@Input
非常感谢@Eliseo,但该解决方案对我现有的代码不起作用(不同的绑定方式,Angular 8?)我变得更加沮丧 - ngControl.control
是总是未定义..
解决方案显然不需要自定义 ErrorStateMatcher
,但答案是确保 mat-select
绑定到 FormGroup
中的 FormControl
,这很繁琐由于生命周期事件,但实际上:
export class DatasetSelectComponent extends AbstractFormFieldComponent {
@Input() label!: string;
@Input() items!: [{id: number, label: string}];
}
export abstract class AbstractFormFieldComponent implements ControlValueAccessor {
// tslint:disable-next-line:variable-name
_formControl = new FormControl();
onChange = (value: any) => {};
constructor(@Self() @Optional() public ngControl: NgControl) {
if(this.ngControl) {
this.ngControl.valueAccessor = this;
}
}
ngAfterViewInit(): void {
if (this.ngControl) {
/**
* get a handle on the FormControl that was created in the last Reactive FormGroup in the component injection hierarchy
* so that it can be bound to the input in our Custom Component
* this ensures input value binding to model + explicit validation is bound
* e.g. new FormGroup({ titleId: new FormControl(personalDetails.titleId, Validators.required) } =>
* <input [formControl]="this.formControl"
* otherwise you will have to do that manually for evey single control on every single form
* which is obviously a lot of repeating yourself
*/
of(this.ngControl.control)
.pipe(
skipWhile(fc => !fc),
take(1)
)
.subscribe(fc => {
this.formControl = fc as FormControl;
console.log(
'Custom FormControl (AbstractFormFieldComponent): Binding to Reactive Form',
this.ngControl,
this.ngControl.control
);
});
}
get formControl() :FormControl|RequiredFormControl {
return this._formControl;
}
set formControl(forControl:FormControl|RequiredFormControl) {
this._formControl = forControl;
}
registerOnChange(fn: (value: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: (value: any) => void): void {}
writeValue(value: any): void {
if(this.formControl) this.formControl.setValue(value, { emitEvent: false });
}
}
注意删除了 NG_VALUE_ACCESSOR
的组件注入(替换为构造函数中的工作),这防止了循环依赖编译时错误:
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => CustomSelectComponent),
}
]
以及模板中的片段:
<mat-select [formControl]="formControl" [required]="formControl.required">
<mat-option *ngFor="let item of items" [value]="item.id">
{{ item.label }}
</mat-option>
</mat-select>
我通过示例设置了这个 stackblitz。
我有一个标准的 input
表单字段和一个显示 select
绑定到数组的自定义字段。
<form [formGroup]="formGroup">
<mat-form-field class="field">
<mat-label>City</mat-label>
<input matInput placeholder="City" formControlName="address1" />
</mat-form-field>
<app-dataset-select label="Country" [items]="countries" formControlName="countryId"></app-dataset-select>
</form>
整个事情都被一个带有验证的表单包装起来:
this.formGroup = new FormGroup({
address1: new FormControl(model.address1, Validators.required),
countryId: new FormControl(model.countryId, Validators.required)
});
当我点击 SAVE 时,我希望两个字段都能明显显示验证 - FormGroup
本身说我们这样做。
但是 Country 控件没有得到 ng-invalid 状态(因此没有发红),我不确定为什么- 虽然它与 angular 的反应形式功夫黑魔法有关...
好吧,如果我们在需要时想要 "asterisk",一种方法是将我们的 mat-input 添加 [required] 属性到我们的内部输入
<mat-select ... [required]="isRequired?true:null">
如何给isRequired变量赋值?
好吧,我喜欢在构造函数中使用并询问是否有属性(*)
constructor(@Attribute('required') required, public injector: Injector) {
this.isRequired=required!=undefined
}
我们使用自定义组件,例如
<app-custom-select placeholder="My State" formControlName="state"
[optionList]="stateList" required>
</app-custom-select>
在那种情况下我们不能包含 Validators.required
A forked stackblitz 包含 *(如果需要)
(*) 我们也可以使用简单的@Input,但是只有当我们想要动态更改值时才必须使用@Input
非常感谢@Eliseo,但该解决方案对我现有的代码不起作用(不同的绑定方式,Angular 8?)我变得更加沮丧 - ngControl.control
是总是未定义..
解决方案显然不需要自定义 ErrorStateMatcher
,但答案是确保 mat-select
绑定到 FormGroup
中的 FormControl
,这很繁琐由于生命周期事件,但实际上:
export class DatasetSelectComponent extends AbstractFormFieldComponent {
@Input() label!: string;
@Input() items!: [{id: number, label: string}];
}
export abstract class AbstractFormFieldComponent implements ControlValueAccessor {
// tslint:disable-next-line:variable-name
_formControl = new FormControl();
onChange = (value: any) => {};
constructor(@Self() @Optional() public ngControl: NgControl) {
if(this.ngControl) {
this.ngControl.valueAccessor = this;
}
}
ngAfterViewInit(): void {
if (this.ngControl) {
/**
* get a handle on the FormControl that was created in the last Reactive FormGroup in the component injection hierarchy
* so that it can be bound to the input in our Custom Component
* this ensures input value binding to model + explicit validation is bound
* e.g. new FormGroup({ titleId: new FormControl(personalDetails.titleId, Validators.required) } =>
* <input [formControl]="this.formControl"
* otherwise you will have to do that manually for evey single control on every single form
* which is obviously a lot of repeating yourself
*/
of(this.ngControl.control)
.pipe(
skipWhile(fc => !fc),
take(1)
)
.subscribe(fc => {
this.formControl = fc as FormControl;
console.log(
'Custom FormControl (AbstractFormFieldComponent): Binding to Reactive Form',
this.ngControl,
this.ngControl.control
);
});
}
get formControl() :FormControl|RequiredFormControl {
return this._formControl;
}
set formControl(forControl:FormControl|RequiredFormControl) {
this._formControl = forControl;
}
registerOnChange(fn: (value: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: (value: any) => void): void {}
writeValue(value: any): void {
if(this.formControl) this.formControl.setValue(value, { emitEvent: false });
}
}
注意删除了 NG_VALUE_ACCESSOR
的组件注入(替换为构造函数中的工作),这防止了循环依赖编译时错误:
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => CustomSelectComponent),
}
]
以及模板中的片段:
<mat-select [formControl]="formControl" [required]="formControl.required">
<mat-option *ngFor="let item of items" [value]="item.id">
{{ item.label }}
</mat-option>
</mat-select>