Angular ControlValueAccessor 和 markAsTouched
Angular ControlValueAccessor and markAsTouched
我正在使用 angular 9 和 Angular Material,并且我通过实现 ControlValueAccessor 接口有一个自定义控件。一切正常。
当表单无效时,在我所有的提交按钮中,我调用 formGroup.markAllAsTouched,因为所有 angular material 字段都变成红色。这样用户可以更好地了解哪些控件无效。
我需要用我的自定义控件实现相同的行为。怎么做?
为了更好地了解情况,我创建了一个 stackblitz 项目here
有 用于将 touched
状态传播到自定义控件的内部 FormControl。
您的简单选择是检查 ngDoCheck
中的状态,一旦自定义控件被触及更新内部 FormControl
的状态:
ngDoCheck() {
if (this.formControl.touched) {
return;
}
if (this.controlDir.control.touched) {
this.formControl.markAsTouched();
}
}
就我个人而言,我不喜欢 ControlValueAccessor
的这种实现方式。
我宁愿使用相同的 FormControl。这可以通过将 viewProviders
和 ControlValueAccessor
提供程序添加到您的自定义控件来完成:
custom-control.component.ts
@Component({
selector: 'my-custom-control',
template: `
<mat-form-field id="userType">
<mat-label>My Custom Component</mat-label>
<mat-select [formControlName]="controlName" (blur)="onTouched()">
<mat-option *ngFor="let current of userTypes" [value]="current.id">{{current.name}}</mat-option>
</mat-select>
</mat-form-field>
`,
viewProviders: [{
provide: ControlContainer,
useFactory: (container: ControlContainer) => container,
deps: [[new SkipSelf(), ControlContainer]],
}]
})
export class MyCustomControl {
@Input() controlName: string;
userTypes: LookupModel[] = [
new LookupModel(1, 'first'),
new LookupModel(2, 'second')
];
}
parent html
<form [formGroup]="form">
<my-custom-control controlName="userTypeCustomControl"></my-custom-control>
另一个机会是使用下面的方法
自定义控件代码
@Component({
selector: 'cvl-advertising-type',
templateUrl: './advertising-type.component.html',
styleUrls: ['./advertising-type.component.scss'],
})
export class AdvertisingTypeComponent implements OnInit, ControlValueAccessor {
advertisingTypes: ReadonlyArray<LookupModel>;
onChange = (value: number) => {
};
onTouched = () => {
};
constructor(private lookupService: LookupService,
@Self() public controlDir: NgControl) {
controlDir.valueAccessor = this;
}
ngOnInit(): void {
this.advertisingTypes = this.lookupService.advertisingTypes;
}
registerOnChange(fn: (value: number) => void): void {
this.controlDir.control.valueChanges.subscribe(fn);
this.onChange = fn;
}
writeValue(value: number): void {
this.controlDir.control.setValue(value);
this.onChange(value);
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.controlDir.control.disable();
} else {
this.controlDir.control.enable();
}
}
}
和parenthtml形式
<cvl-advertising-type [formControl]="form.controls.advertisingType"></cvl-advertising-type>
我在查看此 video
后找到了这个解决方案
最后但并非最不重要的问题是我不明白如何用浅层测试来测试它,因为我有以下错误
Error: NodeInjector: NOT_FOUND [NgControl]
我正在使用 angular 9 和 Angular Material,并且我通过实现 ControlValueAccessor 接口有一个自定义控件。一切正常。
当表单无效时,在我所有的提交按钮中,我调用 formGroup.markAllAsTouched,因为所有 angular material 字段都变成红色。这样用户可以更好地了解哪些控件无效。
我需要用我的自定义控件实现相同的行为。怎么做?
为了更好地了解情况,我创建了一个 stackblitz 项目here
有 touched
状态传播到自定义控件的内部 FormControl。
您的简单选择是检查 ngDoCheck
中的状态,一旦自定义控件被触及更新内部 FormControl
的状态:
ngDoCheck() {
if (this.formControl.touched) {
return;
}
if (this.controlDir.control.touched) {
this.formControl.markAsTouched();
}
}
就我个人而言,我不喜欢 ControlValueAccessor
的这种实现方式。
我宁愿使用相同的 FormControl。这可以通过将 viewProviders
和 ControlValueAccessor
提供程序添加到您的自定义控件来完成:
custom-control.component.ts
@Component({
selector: 'my-custom-control',
template: `
<mat-form-field id="userType">
<mat-label>My Custom Component</mat-label>
<mat-select [formControlName]="controlName" (blur)="onTouched()">
<mat-option *ngFor="let current of userTypes" [value]="current.id">{{current.name}}</mat-option>
</mat-select>
</mat-form-field>
`,
viewProviders: [{
provide: ControlContainer,
useFactory: (container: ControlContainer) => container,
deps: [[new SkipSelf(), ControlContainer]],
}]
})
export class MyCustomControl {
@Input() controlName: string;
userTypes: LookupModel[] = [
new LookupModel(1, 'first'),
new LookupModel(2, 'second')
];
}
parent html
<form [formGroup]="form">
<my-custom-control controlName="userTypeCustomControl"></my-custom-control>
另一个机会是使用下面的方法
自定义控件代码
@Component({
selector: 'cvl-advertising-type',
templateUrl: './advertising-type.component.html',
styleUrls: ['./advertising-type.component.scss'],
})
export class AdvertisingTypeComponent implements OnInit, ControlValueAccessor {
advertisingTypes: ReadonlyArray<LookupModel>;
onChange = (value: number) => {
};
onTouched = () => {
};
constructor(private lookupService: LookupService,
@Self() public controlDir: NgControl) {
controlDir.valueAccessor = this;
}
ngOnInit(): void {
this.advertisingTypes = this.lookupService.advertisingTypes;
}
registerOnChange(fn: (value: number) => void): void {
this.controlDir.control.valueChanges.subscribe(fn);
this.onChange = fn;
}
writeValue(value: number): void {
this.controlDir.control.setValue(value);
this.onChange(value);
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.controlDir.control.disable();
} else {
this.controlDir.control.enable();
}
}
}
和parenthtml形式
<cvl-advertising-type [formControl]="form.controls.advertisingType"></cvl-advertising-type>
我在查看此 video
后找到了这个解决方案最后但并非最不重要的问题是我不明白如何用浅层测试来测试它,因为我有以下错误
Error: NodeInjector: NOT_FOUND [NgControl]