使ControlValueAccessor组件的markAllAsTouched功能可用
首先
我负责Angular Advent Calendar的第211天。Angular提供了FormControl来操作与表单相关的处理。这主要适用于表单系列,但有时可能有多个表单,并希望将它们收集在一个表单中进行处理。ControlValueAccessor就是实现这一目标的手段。虽然这是一个相对小众的话题,我认为写这篇文章的人应该不多,但去年的圣诞日历上有相关内容。
- Angular de Custom Form
这篇文章还介绍了有关验证器的内容,并且毫无疏漏。
如果有人说我们已经没有什么可写的了,那也是不正确的。
虽然在 formControl 中有一个名为 markAllAsTouched() 的方法,但是 ControlValueAccessor 并不支持它。即使在 formGroup 中包含使用 ControlValueAccessor 定义的表单组件并调用 markAllAsTouched(),表单组件内的表单也不会被设置为 touched = true。
那该怎么办呢?本篇文章将进行解释。
请参考上述文章《Angular自定义表单》了解有关ControlValueAccessor的内容。非常感谢。
做完
通过点击“应用”按钮,将值插入到所有下属表单中,并传播“markAllAsTouched()”。
总结
通过定义以下指令,并将该指令应用于希望在调用markAllAsTouched()时生效的表单组件来标记为目标。
import {
ChangeDetectorRef,
Directive,
Inject,
OnInit,
Optional,
Self,
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
NG_VALUE_ACCESSOR,
NgControl,
} from '@angular/forms';
@Directive({
selector: '[appAlteWriteTouched]',
})
export class AlteWriteTouchedDirective implements OnInit {
constructor(
private changeDetectorRef: ChangeDetectorRef,
@Inject(NgControl)
@Optional()
private ngControl?: NgControl,
@Optional()
@Self()
@Inject(NG_VALUE_ACCESSOR)
private valueAccessors?: ControlValueAccessor[]
) {}
controls: AbstractControl[] = [];
ngOnInit(): void {
const control = this.ngControl?.control;
if (!control || !this.valueAccessors || this.valueAccessors.length !== 1) {
return;
}
const controls: AbstractControl[] = Object.values(
this.valueAccessors[0]
).filter((v: unknown) => v instanceof AbstractControl);
if (controls.length === 0) {
return;
}
this.controls = controls;
const parentMarkAllAsTouched = control.markAllAsTouched;
control.markAllAsTouched = (): void => {
parentMarkAllAsTouched.bind(this.ngControl?.control)();
this.controls.forEach((c) => c.markAllAsTouched());
this.changeDetectorRef.markForCheck();
};
}
}
在中文中,只需要一种选择,请用中文将以下内容进行释义:
用途示范
请将其中的一部分提取出来。
请参考完成的 stackbliz 中的整个代码。
另外一提,从 Angular v14 开始,现在可以给 formControl 添加类型。
通过 ControlValueAccessor,现在可以更轻松地进行编码。
<div class="content-wrapper">
<button mat-raised-button (click)="onClickButton()">適用</button>
<app-form1 appAlteWriteTouched [formControl]="form1"></app-form1>
<app-form2 appAlteWriteTouched [formControl]="form2"></app-form2>
</div>
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import {
Form1Type,
Form1Initial,
Form2Type,
Form2Initial,
} from './inputs.interface';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
form1 = new FormControl<Form1Type>(Form1Initial, {
nonNullable: true,
});
form2 = new FormControl<Form2Type>(Form2Initial, {
nonNullable: true,
});
formGroup = new FormGroup({
form1: this.form1,
form2: this.form2,
});
onClickButton(): void {
this.form1.setValue({
a: 'abcdefghijklmn',
b: 1000,
});
this.form2.setValue({
a: 'opqrstuvwxyz',
b: 1000,
});
this.formGroup.markAllAsTouched();
}
}
最后
最近有讨论要在 Angular 的问题 angular/angular#45089 中将 markAsTouched 加入 ControlValueAccessor,但据我看,目前尚未被采纳。
因为这个问题的最后提到了另一种可能的解决方案,所以我决定写这篇文章。
- https://stackblitz.com/edit/angular-ivy-zr9src?file=src%2Fapp%2Ffix-cva.directive.ts
在上面的指令和我的指令之间的区别只是在于不使用 controls[0],而是通过 foreach 循环使用 markAllAsTouched()。但是这样可以将所有子表单的 touched 属性设置为 true。ControlValueAccessor 对于创建具有多个表单的应用程序非常方便,可以限制影响范围。尽管需要更多的知识,但可能会摆脱通过 ngOnChanges 和 detectChanges 进行强制更改检测的束缚。希望将来会变得更加易于使用。这就是 Angular Advent Calendar 的第211天。