如何使用 Validator 和 Control Value Accessor 正确实现嵌套表单?
How to properly implement nested forms with Validator and Control Value Accessor?
在我的应用程序中,我需要一个可重用的嵌套表单组件,例如Address。我希望我的 AddressComponent
处理它自己的 FormGroup,这样我就不需要从外部传递它。
在 Angular 会议 (video, presentation) 上,Angular 核心团队的成员 Kara Erikson 建议为嵌套表单实施 ControlValueAccessor
,使嵌套表单有效地只是一个 FormControl
.
我也想通了,我需要实现Validator
,这样主窗体才能看到我的嵌套窗体的有效性。
最后,我创建了嵌套窗体需要扩展的子窗体class:
export abstract class SubForm implements ControlValueAccessor, Validator {
form: FormGroup;
public onTouched(): void {
}
public writeValue(value: any): void {
if (value) {
this.form.patchValue(value, {emitEvent: false});
this.onTouched();
}
}
public registerOnChange(fn: (x: any) => void): void {
this.form.valueChanges.subscribe(fn);
}
public registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
isDisabled ? this.form.disable()
: this.form.enable();
}
validate(c: AbstractControl): ValidationErrors | null {
return this.form.valid ? null : {subformerror: 'Problems in subform!'};
}
registerOnValidatorChange(fn: () => void): void {
this.form.statusChanges.subscribe(fn);
}
}
如果你想让你的组件以嵌套形式使用,你需要做以下事情:
@Component({
selector: 'app-address',
templateUrl: './address.component.html',
styleUrls: ['./address.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AddressComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => AddressComponent),
multi: true
}
],
})
export class AddressComponent extends SubForm {
constructor(private fb: FormBuilder) {
super();
this.form = this.fb.group({
street: this.fb.control('', Validators.required),
city: this.fb.control('', Validators.required)
});
}
}
一切正常,除非我从主表单的模板中检查子表单的有效性状态。在这种情况下会产生 ExpressionChangedAfterItHasBeenCheckedError
,请参阅 ngIf
语句 (stackblitz code) :
<form action=""
[formGroup]="form"
class="main-form">
<h4>Upper form</h4>
<label>First name</label>
<input type="text"
formControlName="firstName">
<div *ngIf="form.controls['address'].valid">Hi</div>
<app-address formControlName="address"></app-address>
<p>Form:</p>
<pre>{{form.value|json}}</pre>
<p>Validity</p>
<pre>{{form.valid|json}}</pre>
</form>
使用 ChangeDetectorRef
Checks this view and its children. Use in combination with detach to
implement local change detection checks.
This is a cautionary mechanism put in place to prevent inconsistencies
between model data and UI so that erroneous or old data are not shown
to a user on the page
参考:https://angular.io/api/core/ChangeDetectorRef
import { Component, OnInit,ChangeDetectorRef } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-upper',
templateUrl: './upper.component.html',
styleUrls: ['./upper.component.css']
})
export class UpperComponent implements OnInit {
form: FormGroup;
constructor(private fb: FormBuilder,private cdr:ChangeDetectorRef) {
this.form = this.fb.group({
firstName: this.fb.control('', Validators.required),
address: this.fb.control('')
});
}
ngOnInit() {
this.cdr.detectChanges();
}
}
尝试使用 [hidden] 代替 *ngIf,它可以在没有 ChangeDetectorRef 的情况下工作。
更新 URL : https://stackblitz.com/edit/github-3q4znr-ivtrmz?file=src/app/upper/upper.component.html
<div [hidden]="!form.controls['address'].valid">Hi</div>
WriteValue 将在与正常变化检测 lyfe 循环挂钩相同的摘要循环中触发。
要在不使用 changeDetectionRef 的情况下解决此问题,您可以定义有效性状态字段并进行被动更改。
public firstNameValid = false;
this.form.controls.firstName.statusChanges.subscribe(
status => this.firstNameValid = status === 'VALID'
);
<div *ngIf="firstNameValid">Hi</div>
在我的应用程序中,我需要一个可重用的嵌套表单组件,例如Address。我希望我的 AddressComponent
处理它自己的 FormGroup,这样我就不需要从外部传递它。
在 Angular 会议 (video, presentation) 上,Angular 核心团队的成员 Kara Erikson 建议为嵌套表单实施 ControlValueAccessor
,使嵌套表单有效地只是一个 FormControl
.
我也想通了,我需要实现Validator
,这样主窗体才能看到我的嵌套窗体的有效性。
最后,我创建了嵌套窗体需要扩展的子窗体class:
export abstract class SubForm implements ControlValueAccessor, Validator {
form: FormGroup;
public onTouched(): void {
}
public writeValue(value: any): void {
if (value) {
this.form.patchValue(value, {emitEvent: false});
this.onTouched();
}
}
public registerOnChange(fn: (x: any) => void): void {
this.form.valueChanges.subscribe(fn);
}
public registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
isDisabled ? this.form.disable()
: this.form.enable();
}
validate(c: AbstractControl): ValidationErrors | null {
return this.form.valid ? null : {subformerror: 'Problems in subform!'};
}
registerOnValidatorChange(fn: () => void): void {
this.form.statusChanges.subscribe(fn);
}
}
如果你想让你的组件以嵌套形式使用,你需要做以下事情:
@Component({
selector: 'app-address',
templateUrl: './address.component.html',
styleUrls: ['./address.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AddressComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => AddressComponent),
multi: true
}
],
})
export class AddressComponent extends SubForm {
constructor(private fb: FormBuilder) {
super();
this.form = this.fb.group({
street: this.fb.control('', Validators.required),
city: this.fb.control('', Validators.required)
});
}
}
一切正常,除非我从主表单的模板中检查子表单的有效性状态。在这种情况下会产生 ExpressionChangedAfterItHasBeenCheckedError
,请参阅 ngIf
语句 (stackblitz code) :
<form action=""
[formGroup]="form"
class="main-form">
<h4>Upper form</h4>
<label>First name</label>
<input type="text"
formControlName="firstName">
<div *ngIf="form.controls['address'].valid">Hi</div>
<app-address formControlName="address"></app-address>
<p>Form:</p>
<pre>{{form.value|json}}</pre>
<p>Validity</p>
<pre>{{form.valid|json}}</pre>
</form>
使用 ChangeDetectorRef
Checks this view and its children. Use in combination with detach to implement local change detection checks.
This is a cautionary mechanism put in place to prevent inconsistencies between model data and UI so that erroneous or old data are not shown to a user on the page
参考:https://angular.io/api/core/ChangeDetectorRef
import { Component, OnInit,ChangeDetectorRef } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-upper',
templateUrl: './upper.component.html',
styleUrls: ['./upper.component.css']
})
export class UpperComponent implements OnInit {
form: FormGroup;
constructor(private fb: FormBuilder,private cdr:ChangeDetectorRef) {
this.form = this.fb.group({
firstName: this.fb.control('', Validators.required),
address: this.fb.control('')
});
}
ngOnInit() {
this.cdr.detectChanges();
}
}
尝试使用 [hidden] 代替 *ngIf,它可以在没有 ChangeDetectorRef 的情况下工作。
更新 URL : https://stackblitz.com/edit/github-3q4znr-ivtrmz?file=src/app/upper/upper.component.html
<div [hidden]="!form.controls['address'].valid">Hi</div>
WriteValue 将在与正常变化检测 lyfe 循环挂钩相同的摘要循环中触发。
要在不使用 changeDetectionRef 的情况下解决此问题,您可以定义有效性状态字段并进行被动更改。
public firstNameValid = false;
this.form.controls.firstName.statusChanges.subscribe(
status => this.firstNameValid = status === 'VALID'
);
<div *ngIf="firstNameValid">Hi</div>