angular - reactivos - Formas reactivas-marca los campos como tocados
validar formulario angular 5 (11)
Tengo problemas para descubrir cómo marcar todos los campos del formulario como tocados. El problema principal es que si no toco los campos e intento enviar el formulario, no se muestra el error de validación. Tengo un marcador de posición para ese pedazo de código en mi controlador.
Mi idea es simple:
- usuario hace clic en el botón enviar
- todos los campos marcados como tocados
- El formateador de errores se vuelve a ejecutar y muestra los errores de validación.
Si alguien tiene otra idea de cómo mostrar los errores en el envío, sin implementar un nuevo método, por favor compártalos. ¡Gracias!
Mi forma simplificada:
<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>
Y mi controlador:
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] + '' '';
}
}
}
}
}
Así es como lo hago. No quiero que se muestren los campos de error hasta que se presiona el botón de envío (o se toca el formulario).
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>
Entiendo completamente la frustración del OP. Yo uso lo siguiente:
Función de utilidad :
/**
* 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;
}
Uso :
onSubmit() {
if (!formValid(this.formGroup)) {
return;
}
// ... TODO: logic if form is valid.
}
Tenga en cuenta que esta función aún no admite controles anidados.
Esta es mi solucion
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);
}
Este código funciona para mí:
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();
});
}
}
Este es el código que realmente estoy usando.
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);
}
});
}
Hacer un bucle a través de los controles de formulario y marcarlos como tocados también funcionaría:
for(let i in this.form.controls)
this.form.controls[i].markAsTouched();
La siguiente función recurre a través de los controles en un grupo de formularios y los toca suavemente. Debido a que el campo de controles es un objeto, el código llama a Object.values () en el campo de control del grupo de formularios.
/**
* Marks all controls in a form group as touched
* @param formGroup - The group to caress..hah
*/
private markFormGroupTouched(formGroup: FormGroup) {
(<any>Object).values(formGroup.controls).forEach(control => {
control.markAsTouched();
if (control.controls) {
this.markFormGroupTouched(control);
}
});
}
Me encontré con el mismo problema, pero no quiero "contaminar" mis componentes con el código que maneja esto. Especialmente porque necesito esto en muchas formas y no quiero repetir el código en varias ocasiones.
Así que creé una directiva (usando las respuestas publicadas hasta ahora). La directiva decora el método onSubmit de onSubmit
: si el formulario no es válido, marca todos los campos como tocados y cancela el envío. De lo contrario, el método onSubmit habitual se ejecuta normalmente.
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);
};
}
}
Uso:
<form (ngSubmit)="submit()" appValidateOnSubmit>
<!-- ... form controls ... -->
</form>
Respecto a la respuesta de @ masterwork. Intenté esa solución, pero obtuve un error cuando la función intentó excavar, recursivamente, dentro de un FormGroup, porque se pasa un argumento FormControl, en lugar de un FormGroup, en esta línea:
control.controls.forEach(c => this.markFormGroupTouched(c));
Aquí está mi solución
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();
}
});
}
Tuve este problema, pero encontré la forma "correcta" de hacerlo, a pesar de no estar en ningún tutorial angular que haya encontrado.
En su HTML, en la etiqueta de form
, agregue la misma Variable de referencia de plantilla #myVariable=''ngForm''
(variable ''hashtag'') que usan los ejemplos de formularios dirigidos por plantillas, además de lo que usan los ejemplos de formularios reactivos:
<form [formGroup]="myFormGroup" #myForm="ngForm" (ngSubmit)="submit()">
Ahora tiene acceso a myForm.submitted
en la plantilla que puede usar en lugar de (o además de) 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>
Sepa que myForm.form === myFormGroup
es verdadero ... siempre y cuando no olvide la parte ="ngForm"
. Si usa #myForm
solo, no funcionará porque la var se establecerá en el elemento HtmlElement en lugar de la Directiva que controla ese elemento.
Sepa que myFormGroup
es visible en el código mecanografiado de su Componente según los tutoriales de Reactivos, pero myForm
no lo es, a menos que lo pase a través de una llamada de método, como submit(myForm)
para submit(myForm: NgForm): void {...}
. (Tenga en cuenta que NgForm
está en mayúsculas y minúsculas en el texto mecanografiado pero el caso del camello en HTML)
onSubmit(form: any): void {
if (!this.form) {
this.form.markAsTouched();
// this.form.markAsDirty(); <-- this can be useful
}
}