如何将嵌套的 angular 表单标记为已实现 ControlValueAccessor?
How to mark nested angular form as touched implementing ControlValueAccessor?
有 an example 表单 Angular 了解如何将嵌套表单与 ControlValueAccessor 一起使用。
他们创建了一个单独的表单组件并将其用作子表单:
<div [formGroup]="form">
... other form controls
<address-form formControlName="address" legend="Address"></address-form>
</div>
如果我在父表单上调用 markAllAsTouched 方法,我希望将嵌套表单的所有字段标记为已触摸。
<form [formGroup]="form">
<app-address-form formControlName="address"></app-address-form>
</form>
<button (click)="markAsTouched()">Mark as touched</button>
export class AppComponent {
public form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = fb.group({ address: fb.control({ city: '' }) });
}
public markAsTouched() {
this.form.markAllAsTouched();
}
}
是否可以使用这种方法?
我建了一个simplified version on StackBlitz。
当您点击按钮时实际发生的是将应用程序地址表单标记为已触摸:
<app-address-form formcontrolname="address" class="ng-pristine ng-valid ng-touched">
...
</app-address-form>
但我想将其传播到子表单及其字段。
可能有更好的方法,但是,您可以使用 Input
父组件 ts
export class AppComponent {
public form: FormGroup;
setAsTouched: boolean = false;
constructor(private fb: FormBuilder) {
this.form = fb.group({ address: fb.control({ city: '' }) });
}
public markAsTouched() {
this.setAsTouched = true;
}
}
父模板组件
<h1>Form</h1>
<form [formGroup]="form">
<app-address-form
formControlName="address"
[mark]="setAsTouched"
></app-address-form>
</form>
<button (click)="markAsTouched()">Mark as touched</button>
子组件 ts
...
...
export class AddressFormComponent implements ControlValueAccessor, Validator {
@Input() set mark(isTouched: boolean) {
if (isTouched) {
this.form.markAllAsTouched();
}
}
...
...
...
}
确实,解释 link 的嵌套表单组的管理有点混乱,因为您确实 没有 有嵌套表单组。你有一个带一个控件的FormGroup(控件的值是一个对象,但你只有一个控件)
看到你在你的例子中写道:
this.form = fb.group({ address: fb.control({ city: '' }) });
嵌套的 formGroup 应该是这样的:
this.form = fb.group({ address: fb.group({ city: '' }) });
但是这个formGroup不能像大学说的那样管理。方法很简单 使用组件 并将 formGroup 作为 Input(*)
传递
是因为只触及了“控件”
解决方案:您可以使用模板引用变量并传递给您的函数
<form [formGroup]="form">
<app-address-form #address formControlName="address"></app-address-form>
</form>
<button (click)="markAsTouched(address)">Mark as touched</button>
public markAsTouched(address:any) {
this.form.markAllAsTouched();
//see that you can access to the form of the "adress" simply
//using adress.form
address.form.markAllAsTouched()
}
(*) 组件(不是从 ControlValueAccessor 实现的)只是
@Component({
selector: 'app-address-form',
template: `
<div [formGroup]="form">
<label>City</label>
<input formControlName="city" (blur)="onTouched()">
</div>
`
})
export class AddressFormComponent {
form:FormGroup
@Input('form') set _(value){
this.form=value as FormGroup
}
}
而你使用 as
<app-address-form [form]=form.get('address')><app-address-form>
我找到了以下将 markAsTouched 传播到子控件的解决方案。
以下递归函数有帮助:
const updateFormControlTree = (abstractControl: AbstractControl): void => {
const forEachChildControl = (
control: AbstractControl,
callbackFunction: (abstractControl: AbstractControl) => void): void => {
const childControls = (control as AbstractControl & { controls?: [] }).controls;
if (!childControls) {
return;
}
if (typeof childControls === 'object') {
const extractedChildControls: AbstractControl[] =
Object.values(childControls);
extractedChildControls.forEach((childControl) => {
callbackFunction(childControl);
});
}
};
forEachChildControl(abstractControl, (control: AbstractControl) =>
updateFormControlTree(control)
);
abstractControl.markAsTouched();
};
我必须用上面的函数覆盖我的控件的 markAsTouched。为此,我需要 FormControl 而不是 formControlName 属性:
<app-address-form [formControl]="addressControl"></app-address-form>
这就是我重写函数的方法:
export class AddressFormComponent implements ControlValueAccessor, Validator, OnInit
{
@Input() formControl!: FormControl;
public ngOnInit(): void {
this.formControl.markAsTouched = () => updateFormControlTree(this.form);
}
...
}
最后要做的是将我的控件标记为已从父窗体触摸:
export class AppComponent {
public addressControl = new FormControl();
public form: FormGroup = this.fb.group({
address: this.addressControl,
});
constructor(private fb: FormBuilder) {
}
markAsTouched() {
this.addressControl.markAsTouched();
}
}
我建了一个working example on StackBlitz。
P.S。上述解决方案的灵感来自 Angular Framework 源代码中更新值和有效性的方法。这个方法是私有的。不过如果有它就好了 public。
有 an example 表单 Angular 了解如何将嵌套表单与 ControlValueAccessor 一起使用。
他们创建了一个单独的表单组件并将其用作子表单:
<div [formGroup]="form">
... other form controls
<address-form formControlName="address" legend="Address"></address-form>
</div>
如果我在父表单上调用 markAllAsTouched 方法,我希望将嵌套表单的所有字段标记为已触摸。
<form [formGroup]="form">
<app-address-form formControlName="address"></app-address-form>
</form>
<button (click)="markAsTouched()">Mark as touched</button>
export class AppComponent {
public form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = fb.group({ address: fb.control({ city: '' }) });
}
public markAsTouched() {
this.form.markAllAsTouched();
}
}
是否可以使用这种方法?
我建了一个simplified version on StackBlitz。
当您点击按钮时实际发生的是将应用程序地址表单标记为已触摸:
<app-address-form formcontrolname="address" class="ng-pristine ng-valid ng-touched">
...
</app-address-form>
但我想将其传播到子表单及其字段。
可能有更好的方法,但是,您可以使用 Input
父组件 ts
export class AppComponent {
public form: FormGroup;
setAsTouched: boolean = false;
constructor(private fb: FormBuilder) {
this.form = fb.group({ address: fb.control({ city: '' }) });
}
public markAsTouched() {
this.setAsTouched = true;
}
}
父模板组件
<h1>Form</h1>
<form [formGroup]="form">
<app-address-form
formControlName="address"
[mark]="setAsTouched"
></app-address-form>
</form>
<button (click)="markAsTouched()">Mark as touched</button>
子组件 ts
...
...
export class AddressFormComponent implements ControlValueAccessor, Validator {
@Input() set mark(isTouched: boolean) {
if (isTouched) {
this.form.markAllAsTouched();
}
}
...
...
...
}
确实,解释 link 的嵌套表单组的管理有点混乱,因为您确实 没有 有嵌套表单组。你有一个带一个控件的FormGroup(控件的值是一个对象,但你只有一个控件)
看到你在你的例子中写道:
this.form = fb.group({ address: fb.control({ city: '' }) });
嵌套的 formGroup 应该是这样的:
this.form = fb.group({ address: fb.group({ city: '' }) });
但是这个formGroup不能像大学说的那样管理。方法很简单 使用组件 并将 formGroup 作为 Input(*)
传递是因为只触及了“控件”
解决方案:您可以使用模板引用变量并传递给您的函数
<form [formGroup]="form">
<app-address-form #address formControlName="address"></app-address-form>
</form>
<button (click)="markAsTouched(address)">Mark as touched</button>
public markAsTouched(address:any) {
this.form.markAllAsTouched();
//see that you can access to the form of the "adress" simply
//using adress.form
address.form.markAllAsTouched()
}
(*) 组件(不是从 ControlValueAccessor 实现的)只是
@Component({
selector: 'app-address-form',
template: `
<div [formGroup]="form">
<label>City</label>
<input formControlName="city" (blur)="onTouched()">
</div>
`
})
export class AddressFormComponent {
form:FormGroup
@Input('form') set _(value){
this.form=value as FormGroup
}
}
而你使用 as
<app-address-form [form]=form.get('address')><app-address-form>
我找到了以下将 markAsTouched 传播到子控件的解决方案。
以下递归函数有帮助:
const updateFormControlTree = (abstractControl: AbstractControl): void => {
const forEachChildControl = (
control: AbstractControl,
callbackFunction: (abstractControl: AbstractControl) => void): void => {
const childControls = (control as AbstractControl & { controls?: [] }).controls;
if (!childControls) {
return;
}
if (typeof childControls === 'object') {
const extractedChildControls: AbstractControl[] =
Object.values(childControls);
extractedChildControls.forEach((childControl) => {
callbackFunction(childControl);
});
}
};
forEachChildControl(abstractControl, (control: AbstractControl) =>
updateFormControlTree(control)
);
abstractControl.markAsTouched();
};
我必须用上面的函数覆盖我的控件的 markAsTouched。为此,我需要 FormControl 而不是 formControlName 属性:
<app-address-form [formControl]="addressControl"></app-address-form>
这就是我重写函数的方法:
export class AddressFormComponent implements ControlValueAccessor, Validator, OnInit
{
@Input() formControl!: FormControl;
public ngOnInit(): void {
this.formControl.markAsTouched = () => updateFormControlTree(this.form);
}
...
}
最后要做的是将我的控件标记为已从父窗体触摸:
export class AppComponent {
public addressControl = new FormControl();
public form: FormGroup = this.fb.group({
address: this.addressControl,
});
constructor(private fb: FormBuilder) {
}
markAsTouched() {
this.addressControl.markAsTouched();
}
}
我建了一个working example on StackBlitz。
P.S。上述解决方案的灵感来自 Angular Framework 源代码中更新值和有效性的方法。这个方法是私有的。不过如果有它就好了 public。