提交不会在未触及的自定义表单控件元素上触发必需的错误
submit doesn't trigger required error on untouched custom form control element
我使用 Angular 13.1.1 编写我的应用程序。
我有一个带有电子邮件和密码的简单登录表单,我想为电子邮件创建我自己的表单控制组件,添加我自己的验证器和 mat-error
用于错误消息,还允许父组件检测错误并获取值并能够将其用作表单控件。
我遇到的问题是组件外部的提交按钮不会触发 required
错误。通常我会调试自定义表单控件的验证功能,但单击提交按钮不会触发它。
让我们从自定义电子邮件组件开始。
这是 class:
import {ChangeDetectorRef, Component, forwardRef, OnInit} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor, FormBuilder, FormGroup, NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
@Component({
selector: 'app-tuxin-form-email-input',
templateUrl: './tuxin-form-email-input.component.html',
styleUrls: ['./tuxin-form-email-input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi:true,
useExisting: TuxinFormEmailInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: TuxinFormEmailInputComponent
}
]
})
export class TuxinFormEmailInputComponent implements ControlValueAccessor, Validator, OnInit {
newChildForm: FormGroup;
onChange = (email: string) => {};
onTouched = () => {};
onValidationChange: any = () => {};
touched = false;
disabled = false;
constructor(private fb: FormBuilder) {
this.newChildForm = this.fb.group({
email: [null, [Validators.required, Validators.email]],
});
}
writeValue(email: string) {
this.newChildForm.get('email')?.setValue(email, { emitEvent: true });
}
registerOnChange(onChange: any) {
this.onChange = onChange;
}
registerOnTouched(onTouched: any) {
this.onTouched = onTouched;
}
markAsTouched() {
if (!this.touched) {
this.onTouched();
this.touched = true;
}
}
registerOnValidatorChange?(fn: () => void): void {
this.onValidationChange = fn;
}
ngOnInit(): void {
this.newChildForm.valueChanges.subscribe((val) => {
this.onChange(val.email);
this.onValidationChange();
});
}
setDisabledState(disabled: boolean) {
this.disabled = disabled;
disabled ? this.newChildForm.disable() : this.newChildForm.enable();
}
get email() {
return this.newChildForm.get('email');
}
validate(control: AbstractControl): ValidationErrors | null {
if (this.newChildForm?.invalid) {
return { invalid: true };
} else {
return null;
}
}
}
这是模板:
<form [formGroup]="newChildForm">
<mat-form-field>
<mat-label>Email</mat-label>
<input matInput type="email" formControlName="email"/>
<mat-error i18n *ngIf="email?.hasError('required')">Email is required</mat-error>
<mat-error i18n *ngIf="email?.hasError('email')">Email Invalid</mat-error>
</mat-form-field>
</form>
使用邮件组件的组件模板:
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" novalidate>
<div fxLayout="column" fxLayoutAlign="space-around center">
<h4 i18n>Login</h4>
<app-tuxin-form-email-input formControlName="email"></app-tuxin-form-email-input>
<mat-form-field>
<mat-label>Password</mat-label>
<input matInput type="password" formControlName="password" />
<mat-hint i18n>8-30 characters length</mat-hint>
<mat-error i18n *ngIf="password?.hasError('required')">Password is required</mat-error>
</mat-form-field>
<button mat-raised-button type="submit" color="primary" i18n>Login</button>
</div>
</form>
和主成分的class:
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {ToastrService} from 'ngx-toastr';
import {GraphqlService} from '../graphql.service';
@Component({
selector: 'app-login-tab',
templateUrl: './login-tab.component.html',
styleUrls: ['./login-tab.component.scss'],
})
export class LoginTabComponent implements OnInit {
loginForm: FormGroup;
constructor(private formBuilder: FormBuilder, private toastr: ToastrService,
private gql:GraphqlService) {
this.loginForm = this.formBuilder.group({
email: [''],
password: ['', [Validators.required, Validators.min(8), Validators.max(30)]]
});
}
get email() {
return this.loginForm.get('email');
}
get password() {
return this.loginForm.get('password');
}
onSubmit() {
if (this.loginForm.status === 'INVALID') {
this.toastr.error("please fill all forms properly");
} else {
const value = this.loginForm.value;
const email = value.email;
const password = value.password;
this.gql.login(email,password).subscribe(({data})=>{
console.info(data);
})
console.log(value);
}
}
ngOnInit(): void {
}
}
如果我触摸实际的电子邮件组件,错误会立即出现,
但是提交按钮没有启动验证功能,知道为什么以及如何更正它吗?
谢谢
我们遇到了同样的问题。我认为这通常是一个 angular 问题,子 -> 父关系适用于 CVA,但父 -> 子关系更复杂。
特别是因为 email
formControl 在访问 CVA 时未完全绑定到 loginForm
。
我们的解决方案是提交 submitFormEvent$
并将其传递给组件,以便您可以管理何时触发提交验证。
在你的情况下它将是:
import ...
@Component({
selector: 'app-tuxin-form-email-input',
templateUrl: './tuxin-form-email-input.component.html',
styleUrls: ['./tuxin-form-email-input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi:true,
useExisting: TuxinFormEmailInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: TuxinFormEmailInputComponent
}
]
})
export class TuxinFormEmailInputComponent implements ControlValueAccessor, Validator, OnInit {
@Input() submitEvent$ = new Observable<void>()
newChildForm: FormGroup;
onChange = (email: string) => {};
onTouched = () => {};
onValidationChange: any = () => {};
touched = false;
disabled = false;
constructor(private fb: FormBuilder) {
...
}
...
ngOnInit(): void {
this.newChildForm.valueChanges.subscribe((val) => {
this.onChange(val.email);
this.onValidationChange();
});
this.submitEvent$.subscribe(() => {
this.newChildForm.markAsTouched()
})
}
...
}
对于基础组件
import ...
@Component({
selector: 'app-login-tab',
templateUrl: './login-tab.component.html',
styleUrls: ['./login-tab.component.scss'],
})
export class LoginTabComponent implements OnInit {
submitFormEvent$ = new Subject<void>()
loginForm: FormGroup;
constructor(private formBuilder: FormBuilder, private toastr: ToastrService,
private gql:GraphqlService) {
...
}
get email() {
return this.loginForm.get('email');
}
get password() {
return this.loginForm.get('password');
}
onSubmit() {
this.submitFormEvent.next();
if (this.loginForm.status === 'INVALID') {
this.toastr.error("please fill all forms properly");
} else {
const value = this.loginForm.value;
const email = value.email;
const password = value.password;
this.gql.login(email,password).subscribe(({data})=>{
console.info(data);
})
console.log(value);
}
}
ngOnInit(): void {
}
}
我使用 Angular 13.1.1 编写我的应用程序。
我有一个带有电子邮件和密码的简单登录表单,我想为电子邮件创建我自己的表单控制组件,添加我自己的验证器和 mat-error
用于错误消息,还允许父组件检测错误并获取值并能够将其用作表单控件。
我遇到的问题是组件外部的提交按钮不会触发 required
错误。通常我会调试自定义表单控件的验证功能,但单击提交按钮不会触发它。
让我们从自定义电子邮件组件开始。
这是 class:
import {ChangeDetectorRef, Component, forwardRef, OnInit} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor, FormBuilder, FormGroup, NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
@Component({
selector: 'app-tuxin-form-email-input',
templateUrl: './tuxin-form-email-input.component.html',
styleUrls: ['./tuxin-form-email-input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi:true,
useExisting: TuxinFormEmailInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: TuxinFormEmailInputComponent
}
]
})
export class TuxinFormEmailInputComponent implements ControlValueAccessor, Validator, OnInit {
newChildForm: FormGroup;
onChange = (email: string) => {};
onTouched = () => {};
onValidationChange: any = () => {};
touched = false;
disabled = false;
constructor(private fb: FormBuilder) {
this.newChildForm = this.fb.group({
email: [null, [Validators.required, Validators.email]],
});
}
writeValue(email: string) {
this.newChildForm.get('email')?.setValue(email, { emitEvent: true });
}
registerOnChange(onChange: any) {
this.onChange = onChange;
}
registerOnTouched(onTouched: any) {
this.onTouched = onTouched;
}
markAsTouched() {
if (!this.touched) {
this.onTouched();
this.touched = true;
}
}
registerOnValidatorChange?(fn: () => void): void {
this.onValidationChange = fn;
}
ngOnInit(): void {
this.newChildForm.valueChanges.subscribe((val) => {
this.onChange(val.email);
this.onValidationChange();
});
}
setDisabledState(disabled: boolean) {
this.disabled = disabled;
disabled ? this.newChildForm.disable() : this.newChildForm.enable();
}
get email() {
return this.newChildForm.get('email');
}
validate(control: AbstractControl): ValidationErrors | null {
if (this.newChildForm?.invalid) {
return { invalid: true };
} else {
return null;
}
}
}
这是模板:
<form [formGroup]="newChildForm">
<mat-form-field>
<mat-label>Email</mat-label>
<input matInput type="email" formControlName="email"/>
<mat-error i18n *ngIf="email?.hasError('required')">Email is required</mat-error>
<mat-error i18n *ngIf="email?.hasError('email')">Email Invalid</mat-error>
</mat-form-field>
</form>
使用邮件组件的组件模板:
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" novalidate>
<div fxLayout="column" fxLayoutAlign="space-around center">
<h4 i18n>Login</h4>
<app-tuxin-form-email-input formControlName="email"></app-tuxin-form-email-input>
<mat-form-field>
<mat-label>Password</mat-label>
<input matInput type="password" formControlName="password" />
<mat-hint i18n>8-30 characters length</mat-hint>
<mat-error i18n *ngIf="password?.hasError('required')">Password is required</mat-error>
</mat-form-field>
<button mat-raised-button type="submit" color="primary" i18n>Login</button>
</div>
</form>
和主成分的class:
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {ToastrService} from 'ngx-toastr';
import {GraphqlService} from '../graphql.service';
@Component({
selector: 'app-login-tab',
templateUrl: './login-tab.component.html',
styleUrls: ['./login-tab.component.scss'],
})
export class LoginTabComponent implements OnInit {
loginForm: FormGroup;
constructor(private formBuilder: FormBuilder, private toastr: ToastrService,
private gql:GraphqlService) {
this.loginForm = this.formBuilder.group({
email: [''],
password: ['', [Validators.required, Validators.min(8), Validators.max(30)]]
});
}
get email() {
return this.loginForm.get('email');
}
get password() {
return this.loginForm.get('password');
}
onSubmit() {
if (this.loginForm.status === 'INVALID') {
this.toastr.error("please fill all forms properly");
} else {
const value = this.loginForm.value;
const email = value.email;
const password = value.password;
this.gql.login(email,password).subscribe(({data})=>{
console.info(data);
})
console.log(value);
}
}
ngOnInit(): void {
}
}
如果我触摸实际的电子邮件组件,错误会立即出现,
但是提交按钮没有启动验证功能,知道为什么以及如何更正它吗?
谢谢
我们遇到了同样的问题。我认为这通常是一个 angular 问题,子 -> 父关系适用于 CVA,但父 -> 子关系更复杂。
特别是因为 email
formControl 在访问 CVA 时未完全绑定到 loginForm
。
我们的解决方案是提交 submitFormEvent$
并将其传递给组件,以便您可以管理何时触发提交验证。
在你的情况下它将是:
import ...
@Component({
selector: 'app-tuxin-form-email-input',
templateUrl: './tuxin-form-email-input.component.html',
styleUrls: ['./tuxin-form-email-input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi:true,
useExisting: TuxinFormEmailInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: TuxinFormEmailInputComponent
}
]
})
export class TuxinFormEmailInputComponent implements ControlValueAccessor, Validator, OnInit {
@Input() submitEvent$ = new Observable<void>()
newChildForm: FormGroup;
onChange = (email: string) => {};
onTouched = () => {};
onValidationChange: any = () => {};
touched = false;
disabled = false;
constructor(private fb: FormBuilder) {
...
}
...
ngOnInit(): void {
this.newChildForm.valueChanges.subscribe((val) => {
this.onChange(val.email);
this.onValidationChange();
});
this.submitEvent$.subscribe(() => {
this.newChildForm.markAsTouched()
})
}
...
}
对于基础组件
import ...
@Component({
selector: 'app-login-tab',
templateUrl: './login-tab.component.html',
styleUrls: ['./login-tab.component.scss'],
})
export class LoginTabComponent implements OnInit {
submitFormEvent$ = new Subject<void>()
loginForm: FormGroup;
constructor(private formBuilder: FormBuilder, private toastr: ToastrService,
private gql:GraphqlService) {
...
}
get email() {
return this.loginForm.get('email');
}
get password() {
return this.loginForm.get('password');
}
onSubmit() {
this.submitFormEvent.next();
if (this.loginForm.status === 'INVALID') {
this.toastr.error("please fill all forms properly");
} else {
const value = this.loginForm.value;
const email = value.email;
const password = value.password;
this.gql.login(email,password).subscribe(({data})=>{
console.info(data);
})
console.log(value);
}
}
ngOnInit(): void {
}
}