我无法找到如何将所有表单的字段标记为触摸。 主要问题是,如果我不触摸字段并尝试提交表单 - 验证错误未显示。我的控制器中有这段代码的占位符 我的想法很简单:
如果有人知道如何在提交时显示错误,而不实施新方法 - 请分享。谢谢!
我的简化形式:
<form class="form-horizontal" [formGroup]="form" (ngSubmit)="onSubmit(form.value)">
<input type="text" id="title" class="form-control" formControlName="title">
<span class="help-block" *ngIf="formErrors.title">{{ formErrors.title }}</span>
<button>Submit</button>
</form>
我的控制员:
import {Component, OnInit} from '@angular/core';
import {FormGroup, FormBuilder, Validators} from '@angular/forms';
@Component({
selector : 'pastebin-root',
templateUrl: './app.component.html',
styleUrls : ['./app.component.css']
})
export class AppComponent implements OnInit {
form: FormGroup;
formErrors = {
'title': ''
};
validationMessages = {
'title': {
'required': 'Title is required.'
}
};
constructor(private fb: FormBuilder) {
}
ngOnInit(): void {
this.buildForm();
}
onSubmit(form: any): void {
// somehow touch all elements so onValueChanged will generate correct error messages
this.onValueChanged();
if (this.form.valid) {
console.log(form);
}
}
buildForm(): void {
this.form = this.fb.group({
'title': ['', Validators.required]
});
this.form.valueChanges
.subscribe(data => this.onValueChanged(data));
}
onValueChanged(data?: any) {
if (!this.form) {
return;
}
const form = this.form;
for (const field in this.formErrors) {
if (!this.formErrors.hasOwnProperty(field)) {
continue;
}
// clear previous error message (if any)
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.touched && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
if (!control.errors.hasOwnProperty(key)) {
continue;
}
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
}
答案 0 :(得分:83)
以下函数通过表单组中的控件进行递归并轻轻触摸它们。因为controls字段是一个对象,所以代码在表单组的控制字段上调用Object.values()。
$subscriberInfo = [
'subscribers' => [[
'email' => $email,
'custom_fields' =>
['name' => "Bob"]
]
]
];
答案 1 :(得分:7)
关于@masterwork的回答。 我尝试了这个解决方案,但是当函数试图在FormGroup中递归地挖掘时我得到了一个错误,因为在这一行传递了FormControl参数而不是FormGroup:
control.controls.forEach(c => this.markFormGroupTouched(c));
这是我的解决方案
markFormGroupTouched(formGroup: FormGroup) {
(<any>Object).values(formGroup.controls).forEach(control => {
if (control.controls) { // control is a FormGroup
markFormGroupTouched(control);
} else { // control is a FormControl
control.markAsTouched();
}
});
}
答案 2 :(得分:6)
循环使用表单控件并将其标记为已触摸也可以:
for(let i in this.form.controls)
this.form.controls[i].markAsTouched();
答案 3 :(得分:3)
这是我的解决方案
static markFormGroupTouched (FormControls: { [key: string]: AbstractControl } | AbstractControl[]): void {
const markFormGroupTouchedRecursive = (controls: { [key: string]: AbstractControl } | AbstractControl[]): void => {
_.forOwn(controls, (c, controlKey) => {
if (c instanceof FormGroup || c instanceof FormArray) {
markFormGroupTouchedRecursive(c.controls);
} else {
c.markAsTouched();
}
});
};
markFormGroupTouchedRecursive(FormControls);
}
答案 4 :(得分:2)
我有这个问题,但发现这样做的“正确”方式,尽管它没有在我找到过的任何Angular教程中。
在您的HTML中,在form
标记上添加模板驱动的表单示例使用的相同模板引用变量#myVariable='ngForm'
('hashtag'变量),以及Reactive Forms示例使用方法:
<form [formGroup]="myFormGroup" #myForm="ngForm" (ngSubmit)="submit()">
现在您可以访问模板中的myForm.submitted
,而不是myFormGroup.controls.X.touched
可以使用<div *ngIf="myForm.submitted" class="text-error">
<span *ngIf="myFormGroup.controls.myFieldX.errors?.badDate">invalid date format</span>
<span *ngIf="myFormGroup.controls.myFieldX.errors?.isPastDate">date cannot be in the past.</span>
</div>
:
myForm.form === myFormGroup
知道="ngForm"
是真的......只要你不忘记#myForm
部分。如果单独使用myFormGroup
,它将无效,因为var将设置为HtmlElement而不是驱动该元素的Directive。
知道每个Reactive Forms教程在您的Component的打字稿代码中都可以看到myForm
,但是submit(myForm)
不是,除非您通过方法调用将其传递给submit(myForm: NgForm): void {...}
NgForm
。 (注意GetAllMobile : Array<{totalItems: String, totalPages: string,results: Array<any>}>;
是打字稿中的标题大写,但是HTML中的驼峰大小写。)
答案 5 :(得分:2)
对于那些担心性能的人,我想出了一种不使用递归的解决方案,尽管它仍然可以遍历所有级别的所有控件。
/**
* Iterates over a FormGroup or FormArray and mark all controls as
* touched, including its children.
*
* @param {(FormGroup | FormArray)} rootControl - Root form
* group or form array
* @param {boolean} [visitChildren=true] - Specify whether it should
* iterate over nested controls
*/
public markControlsAsTouched(rootControl: FormGroup | FormArray,
visitChildren: boolean = true) {
let stack: (FormGroup | FormArray)[] = [];
// Stack the root FormGroup or FormArray
if (rootControl &&
(rootControl instanceof FormGroup || rootControl instanceof FormArray)) {
stack.push(rootControl);
}
while (stack.length > 0) {
let currentControl = stack.pop();
(<any>Object).values(currentControl.controls).forEach((control) => {
// If there are nested forms or formArrays, stack them to visit later
if (visitChildren &&
(control instanceof FormGroup || control instanceof FormArray)
) {
stack.push(control);
} else {
control.markAsTouched();
}
});
}
}
此解决方案在FormGroup和FormArray上均可使用。
您可以在此处使用它:angular-mark-as-touched
答案 6 :(得分:1)
此代码适用于我:
markAsRequired(formGroup: FormGroup) {
if (Reflect.getOwnPropertyDescriptor(formGroup, 'controls')) {
(<any>Object).values(formGroup.controls).forEach(control => {
if (control instanceof FormGroup) {
// FormGroup
markAsRequired(control);
}
// FormControl
control.markAsTouched();
});
}
}
答案 7 :(得分:1)
按照@masterwork
角度版本8的打字稿代码
private markFormGroupTouched(formGroup: FormGroup) {
(Object as any).values(formGroup.controls).forEach(control => {
control.markAsTouched();
if (control.controls) {
this.markFormGroupTouched(control);
}
}); }
答案 8 :(得分:1)
在Angular v8中,您可以借助markAllAsTouched
方法来实现此内置功能。</ p>
例如,您可以像
一样使用它form.markAllAsTouched();
请参阅正式文档:https://angular.io/api/forms/AbstractControl#markallastouched
答案 9 :(得分:1)
onSubmit(form: any): void {
if (!this.form) {
this.form.markAsTouched();
// this.form.markAsDirty(); <-- this can be useful
}
}
答案 10 :(得分:1)
这是我实际使用的代码。
validateAllFormFields(formGroup: any) {
// This code also works in IE 11
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.validateAllFormFields(control);
} else if (control instanceof FormArray) {
this.validateAllFormFields(control);
}
});
}
&#13;
答案 11 :(得分:1)
我遇到了同样的问题,但我不想&#34;污染&#34;我的组件使用代码处理此问题。特别是因为我需要多种形式,我不想在不同的场合重复这些代码。
因此我创建了一个指令(使用到目前为止发布的答案)。该指令装饰NgForm&#39; onSubmit
- 方法:如果表单无效,则将所有字段标记为已触摸并中止提交。否则通常的onSubmit-Method会正常执行。
import {Directive, Host} from '@angular/core';
import {NgForm} from '@angular/forms';
@Directive({
selector: '[appValidateOnSubmit]'
})
export class ValidateOnSubmitDirective {
constructor(@Host() form: NgForm) {
const oldSubmit = form.onSubmit;
form.onSubmit = function (): boolean {
if (form.invalid) {
const controls = form.controls;
Object.keys(controls).forEach(controlName => controls[controlName].markAsTouched());
return false;
}
return oldSubmit.apply(form, arguments);
};
}
}
用法:
<form (ngSubmit)="submit()" appValidateOnSubmit>
<!-- ... form controls ... -->
</form>
答案 12 :(得分:1)
我是这样做的。我不希望在按下提交按钮(或触摸表单)之前显示错误字段。
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {OnInit} from "@angular/core";
export class MyFormComponent implements OnInit {
doValidation = false;
form: FormGroup;
constructor(fb: FormBuilder) {
this.form = fb.group({
title: ["", Validators.required]
});
}
ngOnInit() {
}
clickSubmitForm() {
this.doValidation = true;
if (this.form.valid) {
console.log(this.form.value);
};
}
}
<form class="form-horizontal" [formGroup]="form" >
<input type="text" class="form-control" formControlName="title">
<div *ngIf="form.get('title').hasError('required') && doValidation" class="alert alert-danger">
title is required
</div>
<button (click)="clickSubmitForm()">Submit</button>
</form>
答案 13 :(得分:0)
请参见this gem。 到目前为止,我所见过的最优雅的解决方案。
完整代码
import { Injectable } from '@angular/core';
import { FormGroup } from '@angular/forms';
const TOUCHED = 'markAsTouched';
const UNTOUCHED = 'markAsUntouched';
const DIRTY = 'markAsDirty';
const PENDING = 'markAsPending';
const PRISTINE = 'markAsPristine';
const FORM_CONTROL_STATES: Array<string> = [TOUCHED, UNTOUCHED, DIRTY, PENDING, PRISTINE];
@Injectable({
providedIn: 'root'
})
export class FormStateService {
markAs (form: FormGroup, state: string): FormGroup {
if (FORM_CONTROL_STATES.indexOf(state) === -1) {
return form;
}
const controls: Array<string> = Object.keys(form.controls);
for (const control of controls) {
form.controls[control][state]();
}
return form;
}
markAsTouched (form: FormGroup): FormGroup {
return this.markAs(form, TOUCHED);
}
markAsUntouched (form: FormGroup): FormGroup {
return this.markAs(form, UNTOUCHED);
}
markAsDirty (form: FormGroup): FormGroup {
return this.markAs(form, DIRTY);
}
markAsPending (form: FormGroup): FormGroup {
return this.markAs(form, PENDING);
}
markAsPristine (form: FormGroup): FormGroup {
return this.markAs(form, PRISTINE);
}
}
答案 14 :(得分:0)
在 Angular 8 + 中,您只需使用
this.form.markAllAsTouched();
将一个控件及其后代控件标记为已触摸。
答案 15 :(得分:0)
我完全理解OP的挫败感。我使用以下内容:
效用函数:
/**
* Determines if the given form is valid by touching its controls
* and updating their validity.
* @param formGroup the container of the controls to be checked
* @returns {boolean} whether or not the form was invalid.
*/
export function formValid(formGroup: FormGroup): boolean {
return !Object.keys(formGroup.controls)
.map(controlName => formGroup.controls[controlName])
.filter(control => {
control.markAsTouched();
control.updateValueAndValidity();
return !control.valid;
}).length;
}
<强>用法强>:
onSubmit() {
if (!formValid(this.formGroup)) {
return;
}
// ... TODO: logic if form is valid.
}
请注意,此功能尚不适合嵌套控件。
答案 16 :(得分:0)
查看:
<button (click)="Submit(yourFormGroup)">Submit</button>
API
Submit(form: any) {
if (form.status === 'INVALID') {
for (let inner in details.controls) {
details.get(inner).markAsTouched();
}
return false;
}
// as it return false it breaks js execution and return
答案 17 :(得分:0)
我制作了一个版本,对给出的答案进行了一些更改,对于那些使用的版本比angular的版本8更旧的人,我想与有用的人分享。
实用功能:
import {FormControl, FormGroup} from "@angular/forms";
function getAllControls(formGroup: FormGroup): FormControl[] {
const controls: FormControl[] = [];
(<any>Object).values(formGroup.controls).forEach(control => {
if (control.controls) { // control is a FormGroup
const allControls = getAllControls(control);
controls.push(...allControls);
} else { // control is a FormControl
controls.push(control);
}
});
return controls;
}
export function isValidForm(formGroup: FormGroup): boolean {
return getAllControls(formGroup)
.filter(control => {
control.markAsTouched();
return !control.valid;
}).length === 0;
}
用法:
onSubmit() {
if (this.isValidForm()) {
// ... TODO: logic if form is valid
}
}
答案 18 :(得分:-1)
/**
* Marks as a touched
* @param { FormGroup } formGroup
*
* @return {void}
*/
markFormGroupTouched(formGroup: FormGroup) {
Object.values(formGroup.controls).forEach((control: any) => {
if (control instanceof FormControl) {
control.markAsTouched();
control.updateValueAndValidity();
} else if (control instanceof FormGroup) {
this.markFormGroupTouched(control);
}
});
}