使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天。

广告
将在 10 秒后关闭
bannerAds