Reactive Form Using ControlValueAccessor for SubForm Show Errors on Submit

廉价感情. 提交于 2019-12-23 20:13:24

问题


I have a simple login page with a reactive form and subform component app-login-form that uses ControlValueAccessor, which is working, but I can't figure out how to display the errors in the subform. This is an example before I start creating more complex forms.

When submitted I try to access the subform and markAllAsTouched, but when I'm watching the elements that the classes don't change.

I made a quick StackBlitz to show what I'm doing. How do I get the error messages to show up when I submit the form?

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

回答1:


I would take a slightly different approach and not use ControlValueAccessor, but instead a "regular" child component and use ControlContainer, then you can skip all that markAsTouched stuff, as parent will be aware of anything going on in child.

parent:

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

template:

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

Child component, where we add formcontrols to existing parent form:

@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]));
  }
}

Then in template you just use formControlName instead of [formControl], like:

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

Also remove the form tags from child, and also remember to add type="button" in the icon, otherwise button will be considered submit.

From parent form submit you can remove: control.markAllAsTouched();

I would have forked your stackblitz with the complete code, but seems like I'm not allowed to fork it. So hopefully I remembered to mention all changes I made, otherwise please provide a stackblitz which can be forked.




回答2:


the problem is that, from parent, you only has a control, when mark as touched mark as touched this control, but not the elements of the login.component. that's you mark as touched 'login', but NOT 'username' nor 'password'. And not, login has no idea about 'username' and 'password' inputs

One solution can be:

You can add to your loginForm.component a function:

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

Then use a variable reference or ViewChild in your app.main

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

with a template reference, simple use in .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()
 }

The other solution is make a custom ErrorStateMatcher, see 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 this SO question you has an example of the customErrorMatcher that show the error when the formControl has errors (it's more complex but it's the ways to do a custom formControl that use internally angular material inputs)




回答3:


Can you try to call updateValueAndValidity behind the markAllAsTouched

control.markAllAsTouched();
control.updateValueAndValidity();

Or you can trigger validation recursive with the code at 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 });
}


来源:https://stackoverflow.com/questions/58160241/reactive-form-using-controlvalueaccessor-for-subform-show-errors-on-submit

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!