如何使用 angulars controlvalueaccessor 触发表单验证
How to trigger validation on form using angulars controlvalueaccessor
在 angular 中使用表单时,通常的方法是直接在表单中使用表单元素(如输入)
<form>
<input>
</form>
提交此表单并且输入具有验证器时,例如。必需的。表单输入已经过验证,如果无效,我们可以向用户显示...很好...
为了能够重用自定义输入,我们可以创建一个包含此输入 + 额外内容的组件。
<form>
<custom-component>
</form>
检查这个 stackblitz:https://stackblitz.com/edit/components-issue-whxtth?file=src%2Fapp%2Fuser%2Fuser.component.html
当点击提交按钮时,只有一个输入被验证。
如果您与两个输入交互,它们将验证
据我所知,在 CVA 组件中没有什么奇怪的。
@Component({
selector: "user",
templateUrl: "./user.component.html",
styleUrls: ["./user.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserComponent implements ControlValueAccessor {
constructor(@Optional() @Self() public ngControl: NgControl) {
if (this.ngControl != null) {
// Setting the value accessor directly (instead of using the providers) to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
}
onTouched = (_value?: any) => {};
onChanged = (_value?: any) => {};
writeValue(val: string): void {}
registerOnChange(fn: any): void {
this.onChanged = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
}
就像我说的,您的输入对验证没有反应的主要原因是您的组件配置为 changeDetection: ChangeDetectionStrategy.OnPush
这将使您的组件仅对受限操作做出反应:
- 输入引用发生变化
- 事件源自组件或其子组件之一
- 在视图中使用异步管道
- 运行 显式更改检测(将提供解决方法)
所以你有两个选择:
- 采用默认的更改检测策略(就像 Angular Material 所做的那样)
- 选择一个丑陋的解决方法。
解决方法如下:
constructor(
@Optional() @Self() public ngControl: NgControl,
private cdr: ChangeDetectorRef // import the CDR
) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
const { _parent } = this.ngControl as any; // this is not a public property
// and refers to the parent form
_parent.ngSubmit.subscribe((r: any) => { // this is fired when the form submitted
this.cdr.detectChanges(); // detect changes manually
});
}
}
这将适用于您将表单作为父表单的情况。通过一些空检查,它将始终如一地工作。但是你可能会遇到其他一些场景,你可能没有机会触发手动更改检测,这会严重损害你的组件的可重用性。
在 angular 中使用表单时,通常的方法是直接在表单中使用表单元素(如输入)
<form>
<input>
</form>
提交此表单并且输入具有验证器时,例如。必需的。表单输入已经过验证,如果无效,我们可以向用户显示...很好...
为了能够重用自定义输入,我们可以创建一个包含此输入 + 额外内容的组件。
<form>
<custom-component>
</form>
检查这个 stackblitz:https://stackblitz.com/edit/components-issue-whxtth?file=src%2Fapp%2Fuser%2Fuser.component.html
当点击提交按钮时,只有一个输入被验证。 如果您与两个输入交互,它们将验证
据我所知,在 CVA 组件中没有什么奇怪的。
@Component({
selector: "user",
templateUrl: "./user.component.html",
styleUrls: ["./user.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserComponent implements ControlValueAccessor {
constructor(@Optional() @Self() public ngControl: NgControl) {
if (this.ngControl != null) {
// Setting the value accessor directly (instead of using the providers) to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
}
onTouched = (_value?: any) => {};
onChanged = (_value?: any) => {};
writeValue(val: string): void {}
registerOnChange(fn: any): void {
this.onChanged = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
}
就像我说的,您的输入对验证没有反应的主要原因是您的组件配置为 changeDetection: ChangeDetectionStrategy.OnPush
这将使您的组件仅对受限操作做出反应:
- 输入引用发生变化
- 事件源自组件或其子组件之一
- 在视图中使用异步管道
- 运行 显式更改检测(将提供解决方法)
所以你有两个选择:
- 采用默认的更改检测策略(就像 Angular Material 所做的那样)
- 选择一个丑陋的解决方法。
解决方法如下:
constructor(
@Optional() @Self() public ngControl: NgControl,
private cdr: ChangeDetectorRef // import the CDR
) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
const { _parent } = this.ngControl as any; // this is not a public property
// and refers to the parent form
_parent.ngSubmit.subscribe((r: any) => { // this is fired when the form submitted
this.cdr.detectChanges(); // detect changes manually
});
}
}
这将适用于您将表单作为父表单的情况。通过一些空检查,它将始终如一地工作。但是你可能会遇到其他一些场景,你可能没有机会触发手动更改检测,这会严重损害你的组件的可重用性。