使用 ControlValueAccessor 的反应式表单在提交时显示错误

Reactive Form Using ControlValueAccessor for SubForm Show Errors on Submit

我有一个简单的登录页面,其中包含反应式表单和使用 ControlValueAccessor 的子表单组件 app-login-form,可以正常工作,但我不知道如何在子表单中显示错误.这是我开始创建更复杂的表单之前的示例。

提交后,我尝试访问子表单和 markAllAsTouched,但是当我查看 类 未更改的元素时。

我快速 StackBlitz 展示了我在做什么。提交表单时如何显示错误消息?

public onSubmit(event: Event): void {
  if (this.form.valid) {
    console.log('VALID', this.form.value);
  } else {
    console.log('INVALID', this.form.value);

    Object.keys(this.form.controls).forEach((controlName) => {
      console.log('SHOW_ERRORS', controlName);
      const control = this.form.get(controlName);
      // ISSUE: Nothing changes on the element still ng-untouched, and 
      // was expecting it to be ng-touched
      control.markAllAsTouched();
    });
  }
}

能不能试试在markAllAsTouched

后面调用updateValueAndValidity
control.markAllAsTouched();
control.updateValueAndValidity();

或者您可以使用 Angular form updateValueAndValidity of all children controls

处的代码触发递归验证
private triggerValidation(control: AbstractControl) {
    if (control instanceof FormGroup) {
        const group = (control as FormGroup);

        for (const field in group.controls) {
            const c = group.controls[field];

            this.triggerValidation(c);
        }
    }
    else if (control instanceof FormArray) {
        const group = (control as FormArray);

        for (const field in group.controls) {
            const c = group.controls[field];

            this.triggerValidation(c);
        }
    }

    control.updateValueAndValidity({ onlySelf: false });
}

问题是,从父级开始,您只有一个控件,当标记为触摸时标记为触摸此控件,而不是 login.component 的元素。那是你标记为感动 'login',但不是 'username' 也不是 'password'。而不是,登录不知道 ​​'username' 和 'password' 输入

一个解决方案可以是:

您可以向 loginForm.component 添加一个函数:

public markAllAsTouched()
{
    this.form.markAllAsTouched();
}

然后在 app.main

中使用变量引用或 ViewChild
@ViewChild(LoginFormComponent,{static:false}) loginForm:LoginFormComponent
//in submit simple
 public onSubmit(event: Event): void {
    if (this.form.valid) {
      console.log('VALID', this.form.value);
    } else {
      console.log('INVALID', this.form.value);
      this.loginForm.markAllAsTouched();
    }
  }

带模板参考,在.html

中简单使用
<form (ngSubmit)="onSubmit(loginForm)">
  <app-login-form #loginForm formControlName="login"></app-login-form>
</form>

 //and in submit
 public onSubmit(loginForm:any): void {
    loginForm.markAllAsTouched()
 }

另一种解决方案是制作自定义 ErrorStateMatcher,请参阅 the docs. angular-material error shows by defect if the control is touched and invalid, but you can change this behaivour to show it if is invalid and "another thing" is touched. (this another thing is the ngControl that you inject) In 您有一个 customErrorMatcher 示例,它在 formControl 出现错误时显示错误(它更复杂,但它是执行自定义 formControl 的方法在内部使用 angular material 输入)

我会采取稍微不同的方法,不使用 ControlValueAccessor,而是使用 "regular" child 组件并使用 ControlContainer,然后您可以跳过所有 markAsTouched 东西,因为 parent 会知道 child.

中发生的任何事情

parent:

this.form = this.formBuilder.group({});

模板:

<app-login-form></app-login-form>

Child 组件,我们将表单控件添加到现有的 parent 表单:

@Component({
  selector: "app-login-form",
  templateUrl: "./login-form.component.html",
  styleUrls: ["./login-form.component.css"],
  viewProviders: [
    {
      provide: ControlContainer,
      useExisting: FormGroupDirective
    }
  ]
})
export class LoginFormComponent implements OnInit {

  childForm: FormGroupDirective;
  constructor(
    parentForm: FormGroupDirective,
    private fb: FormBuilder
    ) {
    this.childForm = parentForm;
  }

  ngOnInit() {
    this.childForm.form.addControl('username', this.fb.control('', [Validators.required]));
    this.childForm.form.addControl('password', this.fb.control('', [Validators.required]));
  }
}

然后在模板中你只需使用 formControlName 而不是 [formControl],例如:

 <input matInput formControlName="username">
 <mat-error *ngIf="childForm.hasError('required', 'username')">Required</mat-error>

同时去掉child中的表单标签,同时记得在图标中添加type="button",否则按钮将被视为submit

从 parent 表单提交您可以删除:control.markAllAsTouched();

我会用完整的代码分叉你的 stackblitz,但似乎不允许我分叉它。所以希望我记得提及我所做的所有更改,否则请提供可以分叉的 stackblitz。