如何将嵌套的角度形式标记为触摸实现ControlValueAccessor?

问题描述 投票:0回答:4

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();
  }
}

可以使用这种方法吗?

我在 StackBlitz 上构建了一个 简化版本

单击按钮时实际发生的情况是将应用程序地址表单标记为已触摸:

<app-address-form formcontrolname="address" class="ng-pristine ng-valid ng-touched">
...
</app-address-form>

但我想将其传播到子表单及其字段。

angular angular-reactive-forms
4个回答
2
投票

我找到了以下将 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();
  }
}

我在 StackBlitz 上构建了一个 工作示例

附注上述解决方案的灵感来自于 Angular Framework 源代码中的更新值和有效性的方法。这个方法是私有的。不过如果能公开就好了。


0
投票

可能有更好的方法,但是,您可以通过使用

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();
    }
  }

  ...
  ...
  ...
}

0
投票

实际上,解释链接的嵌套表单组的管理有点混乱,因为您确实没有有嵌套表单组。你有一个带有一个控件的 FormGroup(控件的值是一个对象,但你只有一个控件)

看到在你的例子中你写的是:

 this.form = fb.group({ address: fb.control({ city: '' }) });

嵌套的 formGroup 应该是这样的:

 this.form = fb.group({ address: fb.group({ city: '' }) });

但是这个formGroup不能像Angular大学所说的那样进行管理。方法很简单:使用组件并将 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()
  }

(*) 组件(不是从 ControlValueAccesor 实现)只是

@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>

0
投票

这在 Angular 18+ 中可以使用 formControl.events observable 实现。

parentControl.events
  .pipe(
    filter(e => e instanceof TouchedChangeEvent),
    filter(e => e.touched),
    tap(() => childControl.markAllAsTouched())
  ).subscribe()
© www.soinside.com 2019 - 2024. All rights reserved.