问题
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