Angular 2 响应式表单在提交时触发验证
Angular 2 Reactive Forms trigger validation on submit
有没有一种方法可以在提交时触发反应式表单的所有验证器,而不仅仅是 "dirty" 和 "touch" 事件?
原因是我们有一个非常大的表单,它不指示某个字段是否为必填项,用户可能会错过一些必需的控件,因此在提交时,预计所有将显示最终用户遗漏的无效字段。
我尝试使用
将表单标记为 "touched"
FormGroup.markAsTouched(true);
它起作用了,所以我也尝试将其标记为 "dirty"
FormGroup.markAsDirty(true);
但是 class 的 css 仍然是 "ng-pristine",
有没有办法从组件手动触发它,我尝试谷歌搜索它无济于事,提前谢谢你!
更新
我已经通过迭代 FormGroup.controls 并将其标记为 "dirty" 使其工作,但是是否有 "standard" 方法可以做到这一点。
时隔数月归来,在此分享根据大家的评论改进后的版本,仅供记录:
markAsTouched(group: FormGroup | FormArray) {
group.markAsTouched({ onlySelf: true });
Object.keys(group.controls).map((field) => {
const control = group.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.markAsTouched(control);
}
});
}
希望有用!
更新:Angular 8 引入了 FormGroup.markAllAsTouched()
并且它做到了! :D
我发现了一些可能感兴趣的东西:
在提交时,我设置了 submitAttempt = true
并将其放在应该进行验证的 div 中:
nickname.touched || nickname.dirty || (nickname.untouched && submitAttempt)
意思是:
如果没有被触及,我们尝试提交,错误显示。
这个可以通过markAsTouched()
. Until PR #26812合并来完成,可以用
function markAllAsTouched(group: AbstractControl) {
group.markAsTouched({onlySelf: true});
group._forEachChild((control: AbstractControl) => markAllAsTouched(control));
}
您可以在 source code 中找到更多信息。
有多种方法可以解决这个问题。如果您有嵌套的表单组,@Splaktar 的答案将不起作用。因此,这是适用于嵌套表单组的解决方案。
Solution 1: Iterate through all formgroups and formcontrols and programmatically touch them to trigger validations.
模板代码:
<form [formGroup]="myForm" (ngSubmit)="onSubmit()" novalidate>
...
<button type="submit" class="btn btn-success">Save</button>
</form>
component.ts代码:
onSubmit() {
if (this.myForm.valid) {
// save data
} else {
this.validateAllFields(this.myForm);
}
}
validateAllFields(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.validateAllFields(control);
}
});
}
Solution 2: Use a variable to check if the form has been submitted or not. FYI: The submitted field for the ngForm is currently being tested and will be included in future Angular versions. So there will not be a need to create your own variable.
component.ts代码
private formSubmitAttempt: boolean;
onSubmit() {
this.formSubmitAttempt = true;
if (this.myForm.valid) {
console.log('form submitted');
}
}
模板代码:
<form [formGroup]="myForm" (ngSubmit)="onSubmit()" novalidate>
<div class="form-group">
<label class="center-block">
Name:
<input class="form-control" formControlName="name">
</label>
<div class="alert alert-danger" *ngIf="myForm.get('name').hasError('required') && formSubmitAttempt">
Name is required
</div>
...
</form>
这可以通过提供的示例 here 实现,您可以在其中使用 NgForm
指令:
<form [formGroup]="heroForm" #formDir="ngForm">
然后在您的验证消息中检查表单是否已提交:
<small *ngIf="heroForm.hasError('required', 'formCtrlName') && formDir.submitted">
Required!
</small>
编辑:现在也提供了 { updateOn: 'submit'}
,但只有在表单上没有 required
时才有效,因为 required
无论如何最初都会显示。您可以通过检查字段是否已被触摸来抑制它。
// fb is 'FormBuilder'
this.heroForm = this.fb.group({
// ...
}, { updateOn: 'submit'})
我的应用程序有很多表单和输入,所以我创建了各种自定义表单组件(用于普通文本输入、textarea 输入、选择、复选框等),这样我就不需要重复冗长 HTML/CSS 和表单验证 UI 逻辑无处不在。
除了 FormControl
状态(valid
、touched
, 等) 来决定需要在 UI.
上显示哪些验证状态和消息(如果有的话)
这个解决方案
- 不需要遍历表单的控件并修改它们的状态
- 不需要向每个控件添加一些额外的
submitted
属性
- 在
ngSubmit
绑定的 onSubmit
方法中不需要任何额外的表单验证处理
- 不将模板驱动的表单与响应式表单相结合
形式-base.component:
import {Host, Input, OnInit, SkipSelf} from '@angular/core';
import {FormControl, FormGroupDirective} from '@angular/forms';
export abstract class FormBaseComponent implements OnInit {
@Input() id: string;
@Input() label: string;
formControl: FormControl;
constructor(@Host() @SkipSelf()
private formControlHost: FormGroupDirective) {
}
ngOnInit() {
const form = this.formControlHost.form;
this.formControl = <FormControl>form.controls[this.id];
if (!this.formControl) {
throw new Error('FormControl \'' + this.id + '\' needs to be defined');
}
}
get errorMessage(): string {
// TODO return error message based on 'this.formControl.errors'
return null;
}
get showInputValid(): boolean {
return this.formControl.valid && (this.formControl.touched || this.formControlHost.submitted);
}
get showInputInvalid(): boolean {
return this.formControl.invalid && (this.formControl.touched || this.formControlHost.submitted);
}
}
形式-text.component:
import {Component} from '@angular/core';
import {FormBaseComponent} from '../form-base.component';
@Component({
selector: 'yourappprefix-form-text',
templateUrl: './form-text.component.html'
})
export class FormTextComponent extends FormBaseComponent {
}
形式-text.component.html:
<label class="x_label" for="{{id}}">{{label}}</label>
<div class="x_input-container"
[class.x_input--valid]="showInputValid"
[class.x_input--invalid]="showInputInvalid">
<input class="x_input" id="{{id}}" type="text" [formControl]="formControl">
<span class="x_input--error-message" *ngIf="errorMessage">{{errorMessage}}</span>
</div>
用法:
<form [formGroup]="form" novalidate>
<yourappprefix-form-text id="someField" label="Some Field"></yourappprefix-form-text>
</form>
如果我得到你想要的。您只想在每次提交时更新验证消息。最好的方法是存储控件状态的历史记录。
export interface IValdiationField {
submittedCount: number;
valid: boolean;
}
class Component {
// validation state management
validationState: Map<string, IValdiationField | number> = new Map();
constructor() {
this.validationState.set('submitCount', 0);
}
validationChecker(formControlName: string): boolean {
// get submitted count
const submittedCount: number = (this.validationState.get('submitCount') || 0) as number;
// form shouldn't show validation if form not submitted
if (submittedCount === 0) {
return true;
}
// get the validation state
const state: IValdiationField = this.validationState.get(formControlName) as IValdiationField;
// set state if undefined or state submitted count doesn't match submitted count
if (state === undefined || state.submittedCount !== submittedCount) {
this.validationState.set(formControlName, { submittedCount, valid: this.form.get(formControlName).valid } );
return this.form.get(formControlName).valid;
}
// get validation value from validation state managment
return state.valid;
}
submit() {
this.validationState.set('submitCount', (this.validationState.get('submitCount') as number) + 1);
}
}
然后在 html 代码 *ngIf="!validationChecker('formControlName')" 中显示错误信息。
"dirty"、"touched"、"submitted" 可以使用下一个方法组合:
<form [formGroup]="form" (ngSubmit)="doSomething()" #ngForm="ngForm">
<input type="text" placeholder="Put some text" formControlName="textField" required>
<div *ngIf="textField.invalid && (textField.dirty || textField.touched || ngForm.submitted)">
<div *ngIf="textField.errors.required">Required!</div>
</div>
<input type="submit" value="Submit" />
</form>
现在有 updateOn:'submit' 选项,它会在提交时触发验证,使用如下:
this.myForm = new FormGroup({},{updateOn: ‘submit’});
使用开箱即用的验证器,最好的方法就是在用户点击提交时检查表单组是否有效。
markAllAsTouched()
然后可用于触发对表单每个字段的有效性检查:
// Component
loginForm: FormGroup;
ngOnInit() {
this.loginForm = new FormGroup({
username: new FormControl(null, [
Validators.required,
Validators.minLength(4)
]),
password: new FormControl(null, [
Validators.required,
Validators.minLength(4)
]),
});
}
submitLoginForm() {
if (!this.loginForm.invalid) { // Checks form input validity
// Form input is valid
console.log('Valid login attempt - allow submission');
} else {
// Form input is not valid
this.loginForm.markAllAsTouched(); // Trigger validation across form
console.log('Invalid login attempt - block submission');
}
}
// Template
<form id="login_wrapper" [formGroup]="loginForm" (ngSubmit)="submitLoginForm()">
<h1>Log in</h1>
<mat-form-field color="accent">
<mat-label>Username</mat-label>
<input matInput
placeholder="Username"
type="text"
formControlName="username">
<mat-error
*ngIf="loginForm.controls.username.invalid && (loginForm.controls.username.dirty || loginForm.controls.username.touched)">
Please enter a valid username
</mat-error>
</mat-form-field>
<mat-form-field color="accent">
<mat-label>Password</mat-label>
<input matInput
placeholder="Password"
type="password"
formControlName="password">
<mat-error
*ngIf="loginForm.controls.password.invalid && (loginForm.controls.password.dirty || loginForm.controls.password.touched)">
Please enter a valid password
</mat-error>
</mat-form-field>
<button id="login_btn"
mat-flat-button
color="primary"
type="submit">Login</button>
</form>
有时:
- 你不想添加 ngForm 只是为了知道控制器是否已经提交
- 您想知道是否有任何控制器属于已提交的表单,即是否有已提交的祖先。
使用下面的函数,您可以将任何 Control
标记为已提交(FormControl
、FormGroup
、ArrayGroup
)并检查 Control
是否为或已提交 parent.
import { AbstractControl as AngularAbstractControl } from '@angular/forms';
const isSubmittedSymbol = Symbol('This control is flagged as submitted');
/**
* Return true if the form control or it's parent has been flagged as submitted
*/
export function isSubmitted(control: AngularAbstractControl): boolean {
if (control.parent) {
return isSubmitted(control.parent);
}
return !!control[isSubmittedSymbol];
}
/**
* Flag the form control
*/
export function setSubmitted(control: AngularAbstractControl, submitted: boolean = true) {
control[isSubmittedSymbol] = submitted;
}
虚拟示例:
public onSubmit(){
submitted(this.myFormGroup);
// your logic
}
public onReset(){
submitted(this.myFormGroup, false);
}
public isSubmitted(){
isSubmitted(this.myFormGroup);
}
<form [formGroup]="myFormGroup">
your form logic here
</form>
<button (click)="onSubmit()"
[disabled]=isSubmitted()>
</button>
注意:由于 isSubmitted()
函数递归检查 parent 是否已提交,您当然可以将它用于 FormArray
中的 FormControl
[=] 15=](或任何配置)。只要根 parent 会被标记为 isSubmitted,所有 children 实际上都会被标记为相同的。
有没有一种方法可以在提交时触发反应式表单的所有验证器,而不仅仅是 "dirty" 和 "touch" 事件?
原因是我们有一个非常大的表单,它不指示某个字段是否为必填项,用户可能会错过一些必需的控件,因此在提交时,预计所有将显示最终用户遗漏的无效字段。
我尝试使用
将表单标记为 "touched"FormGroup.markAsTouched(true);
它起作用了,所以我也尝试将其标记为 "dirty"
FormGroup.markAsDirty(true);
但是 class 的 css 仍然是 "ng-pristine",
有没有办法从组件手动触发它,我尝试谷歌搜索它无济于事,提前谢谢你!
更新
我已经通过迭代 FormGroup.controls 并将其标记为 "dirty" 使其工作,但是是否有 "standard" 方法可以做到这一点。
时隔数月归来,在此分享根据大家的评论改进后的版本,仅供记录:
markAsTouched(group: FormGroup | FormArray) {
group.markAsTouched({ onlySelf: true });
Object.keys(group.controls).map((field) => {
const control = group.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.markAsTouched(control);
}
});
}
希望有用!
更新:Angular 8 引入了 FormGroup.markAllAsTouched()
并且它做到了! :D
我发现了一些可能感兴趣的东西:
在提交时,我设置了 submitAttempt = true
并将其放在应该进行验证的 div 中:
nickname.touched || nickname.dirty || (nickname.untouched && submitAttempt)
意思是: 如果没有被触及,我们尝试提交,错误显示。
这个可以通过markAsTouched()
. Until PR #26812合并来完成,可以用
function markAllAsTouched(group: AbstractControl) {
group.markAsTouched({onlySelf: true});
group._forEachChild((control: AbstractControl) => markAllAsTouched(control));
}
您可以在 source code 中找到更多信息。
有多种方法可以解决这个问题。如果您有嵌套的表单组,@Splaktar 的答案将不起作用。因此,这是适用于嵌套表单组的解决方案。
Solution 1: Iterate through all formgroups and formcontrols and programmatically touch them to trigger validations.
模板代码:
<form [formGroup]="myForm" (ngSubmit)="onSubmit()" novalidate>
...
<button type="submit" class="btn btn-success">Save</button>
</form>
component.ts代码:
onSubmit() {
if (this.myForm.valid) {
// save data
} else {
this.validateAllFields(this.myForm);
}
}
validateAllFields(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.validateAllFields(control);
}
});
}
Solution 2: Use a variable to check if the form has been submitted or not. FYI: The submitted field for the ngForm is currently being tested and will be included in future Angular versions. So there will not be a need to create your own variable.
component.ts代码
private formSubmitAttempt: boolean;
onSubmit() {
this.formSubmitAttempt = true;
if (this.myForm.valid) {
console.log('form submitted');
}
}
模板代码:
<form [formGroup]="myForm" (ngSubmit)="onSubmit()" novalidate>
<div class="form-group">
<label class="center-block">
Name:
<input class="form-control" formControlName="name">
</label>
<div class="alert alert-danger" *ngIf="myForm.get('name').hasError('required') && formSubmitAttempt">
Name is required
</div>
...
</form>
这可以通过提供的示例 here 实现,您可以在其中使用 NgForm
指令:
<form [formGroup]="heroForm" #formDir="ngForm">
然后在您的验证消息中检查表单是否已提交:
<small *ngIf="heroForm.hasError('required', 'formCtrlName') && formDir.submitted">
Required!
</small>
编辑:现在也提供了 { updateOn: 'submit'}
,但只有在表单上没有 required
时才有效,因为 required
无论如何最初都会显示。您可以通过检查字段是否已被触摸来抑制它。
// fb is 'FormBuilder'
this.heroForm = this.fb.group({
// ...
}, { updateOn: 'submit'})
我的应用程序有很多表单和输入,所以我创建了各种自定义表单组件(用于普通文本输入、textarea 输入、选择、复选框等),这样我就不需要重复冗长 HTML/CSS 和表单验证 UI 逻辑无处不在。
除了 FormControl
状态(valid
、touched
, 等) 来决定需要在 UI.
这个解决方案
- 不需要遍历表单的控件并修改它们的状态
- 不需要向每个控件添加一些额外的
submitted
属性 - 在
ngSubmit
绑定的onSubmit
方法中不需要任何额外的表单验证处理 - 不将模板驱动的表单与响应式表单相结合
形式-base.component:
import {Host, Input, OnInit, SkipSelf} from '@angular/core';
import {FormControl, FormGroupDirective} from '@angular/forms';
export abstract class FormBaseComponent implements OnInit {
@Input() id: string;
@Input() label: string;
formControl: FormControl;
constructor(@Host() @SkipSelf()
private formControlHost: FormGroupDirective) {
}
ngOnInit() {
const form = this.formControlHost.form;
this.formControl = <FormControl>form.controls[this.id];
if (!this.formControl) {
throw new Error('FormControl \'' + this.id + '\' needs to be defined');
}
}
get errorMessage(): string {
// TODO return error message based on 'this.formControl.errors'
return null;
}
get showInputValid(): boolean {
return this.formControl.valid && (this.formControl.touched || this.formControlHost.submitted);
}
get showInputInvalid(): boolean {
return this.formControl.invalid && (this.formControl.touched || this.formControlHost.submitted);
}
}
形式-text.component:
import {Component} from '@angular/core';
import {FormBaseComponent} from '../form-base.component';
@Component({
selector: 'yourappprefix-form-text',
templateUrl: './form-text.component.html'
})
export class FormTextComponent extends FormBaseComponent {
}
形式-text.component.html:
<label class="x_label" for="{{id}}">{{label}}</label>
<div class="x_input-container"
[class.x_input--valid]="showInputValid"
[class.x_input--invalid]="showInputInvalid">
<input class="x_input" id="{{id}}" type="text" [formControl]="formControl">
<span class="x_input--error-message" *ngIf="errorMessage">{{errorMessage}}</span>
</div>
用法:
<form [formGroup]="form" novalidate>
<yourappprefix-form-text id="someField" label="Some Field"></yourappprefix-form-text>
</form>
如果我得到你想要的。您只想在每次提交时更新验证消息。最好的方法是存储控件状态的历史记录。
export interface IValdiationField {
submittedCount: number;
valid: boolean;
}
class Component {
// validation state management
validationState: Map<string, IValdiationField | number> = new Map();
constructor() {
this.validationState.set('submitCount', 0);
}
validationChecker(formControlName: string): boolean {
// get submitted count
const submittedCount: number = (this.validationState.get('submitCount') || 0) as number;
// form shouldn't show validation if form not submitted
if (submittedCount === 0) {
return true;
}
// get the validation state
const state: IValdiationField = this.validationState.get(formControlName) as IValdiationField;
// set state if undefined or state submitted count doesn't match submitted count
if (state === undefined || state.submittedCount !== submittedCount) {
this.validationState.set(formControlName, { submittedCount, valid: this.form.get(formControlName).valid } );
return this.form.get(formControlName).valid;
}
// get validation value from validation state managment
return state.valid;
}
submit() {
this.validationState.set('submitCount', (this.validationState.get('submitCount') as number) + 1);
}
}
然后在 html 代码 *ngIf="!validationChecker('formControlName')" 中显示错误信息。
"dirty"、"touched"、"submitted" 可以使用下一个方法组合:
<form [formGroup]="form" (ngSubmit)="doSomething()" #ngForm="ngForm">
<input type="text" placeholder="Put some text" formControlName="textField" required>
<div *ngIf="textField.invalid && (textField.dirty || textField.touched || ngForm.submitted)">
<div *ngIf="textField.errors.required">Required!</div>
</div>
<input type="submit" value="Submit" />
</form>
现在有 updateOn:'submit' 选项,它会在提交时触发验证,使用如下:
this.myForm = new FormGroup({},{updateOn: ‘submit’});
使用开箱即用的验证器,最好的方法就是在用户点击提交时检查表单组是否有效。
markAllAsTouched()
然后可用于触发对表单每个字段的有效性检查:
// Component
loginForm: FormGroup;
ngOnInit() {
this.loginForm = new FormGroup({
username: new FormControl(null, [
Validators.required,
Validators.minLength(4)
]),
password: new FormControl(null, [
Validators.required,
Validators.minLength(4)
]),
});
}
submitLoginForm() {
if (!this.loginForm.invalid) { // Checks form input validity
// Form input is valid
console.log('Valid login attempt - allow submission');
} else {
// Form input is not valid
this.loginForm.markAllAsTouched(); // Trigger validation across form
console.log('Invalid login attempt - block submission');
}
}
// Template
<form id="login_wrapper" [formGroup]="loginForm" (ngSubmit)="submitLoginForm()">
<h1>Log in</h1>
<mat-form-field color="accent">
<mat-label>Username</mat-label>
<input matInput
placeholder="Username"
type="text"
formControlName="username">
<mat-error
*ngIf="loginForm.controls.username.invalid && (loginForm.controls.username.dirty || loginForm.controls.username.touched)">
Please enter a valid username
</mat-error>
</mat-form-field>
<mat-form-field color="accent">
<mat-label>Password</mat-label>
<input matInput
placeholder="Password"
type="password"
formControlName="password">
<mat-error
*ngIf="loginForm.controls.password.invalid && (loginForm.controls.password.dirty || loginForm.controls.password.touched)">
Please enter a valid password
</mat-error>
</mat-form-field>
<button id="login_btn"
mat-flat-button
color="primary"
type="submit">Login</button>
</form>
有时:
- 你不想添加 ngForm 只是为了知道控制器是否已经提交
- 您想知道是否有任何控制器属于已提交的表单,即是否有已提交的祖先。
使用下面的函数,您可以将任何 Control
标记为已提交(FormControl
、FormGroup
、ArrayGroup
)并检查 Control
是否为或已提交 parent.
import { AbstractControl as AngularAbstractControl } from '@angular/forms';
const isSubmittedSymbol = Symbol('This control is flagged as submitted');
/**
* Return true if the form control or it's parent has been flagged as submitted
*/
export function isSubmitted(control: AngularAbstractControl): boolean {
if (control.parent) {
return isSubmitted(control.parent);
}
return !!control[isSubmittedSymbol];
}
/**
* Flag the form control
*/
export function setSubmitted(control: AngularAbstractControl, submitted: boolean = true) {
control[isSubmittedSymbol] = submitted;
}
虚拟示例:
public onSubmit(){
submitted(this.myFormGroup);
// your logic
}
public onReset(){
submitted(this.myFormGroup, false);
}
public isSubmitted(){
isSubmitted(this.myFormGroup);
}
<form [formGroup]="myFormGroup">
your form logic here
</form>
<button (click)="onSubmit()"
[disabled]=isSubmitted()>
</button>
注意:由于 isSubmitted()
函数递归检查 parent 是否已提交,您当然可以将它用于 FormArray
中的 FormControl
[=] 15=](或任何配置)。只要根 parent 会被标记为 isSubmitted,所有 children 实际上都会被标记为相同的。