Ruft alle Validierungsfehler von Angular 2 FormGroup ab


91

Angesichts dieses Codes:

this.form = this.formBuilder.group({
      email: ['', [Validators.required, EmailValidator.isValid]],
      hasAcceptedTerms: [false, Validators.pattern('true')]
    });

Wie kann ich alle Validierungsfehler erhalten this.form?

Ich schreibe Unit-Tests und möchte die tatsächlichen Validierungsfehler in die Assert-Nachricht aufnehmen.


Anstelle von Validators.pattern ('true') können / sollten Sie Validators.requiredTrue verwenden, um das Aktivieren des Kontrollkästchens zu erzwingen.
Nichtig

Antworten:


142

Ich bin auf dasselbe Problem gestoßen, und um alle Validierungsfehler zu finden und anzuzeigen, habe ich die nächste Methode geschrieben:

getFormValidationErrors() {
  Object.keys(this.productForm.controls).forEach(key => {

  const controlErrors: ValidationErrors = this.productForm.get(key).errors;
  if (controlErrors != null) {
        Object.keys(controlErrors).forEach(keyError => {
          console.log('Key control: ' + key + ', keyError: ' + keyError + ', err value: ', controlErrors[keyError]);
        });
      }
    });
  }

Der Formularname productFormsollte in Ihren geändert werden.

Es funktioniert folgendermaßen: Wir erhalten alle Steuerelemente aus dem Formular im Format {[p: string]: AbstractControl}und iterieren mit jedem Fehlerschlüssel, um Details zum Fehler abzurufen. Es werden nullFehlerwerte übersprungen.

Es kann auch geändert werden, um Validierungsfehler in der Vorlagenansicht anzuzeigen. Ersetzen console.log(..)Sie es einfach durch das , was Sie benötigen.


2
Wie kann die obige Methode für FormArray im selben Muster erweitert werden?
Mohammad Sharaf Ali

Meinten Sie ' + controlErrors[keyErrors];statt ', controlErrors[keyErrors];?
Ryanm

@ryanm nein, es gibt Unterschiede beim Drucken von Objekten oder Zeichenfolgenwerten.
Alex Efimov

Woher kann ich ValidationErrorsin Winkel 2 importieren ?
Sainu

import { ValidationErrors } from '@angular/forms';
Craig Wayne

31

Dies ist eine Lösung mit FormGroupInnenstützen ( wie hier )

Getestet am: Angular 4.3.6

get-form-validation-error.ts

import { AbstractControl, FormGroup, ValidationErrors } from '@angular/forms';

export interface AllValidationErrors {
  control_name: string;
  error_name: string;
  error_value: any;
}

export interface FormGroupControls {
  [key: string]: AbstractControl;
}

export function getFormValidationErrors(controls: FormGroupControls): AllValidationErrors[] {
  let errors: AllValidationErrors[] = [];
  Object.keys(controls).forEach(key => {
    const control = controls[ key ];
    if (control instanceof FormGroup) {
      errors = errors.concat(getFormValidationErrors(control.controls));
    }
    const controlErrors: ValidationErrors = controls[ key ].errors;
    if (controlErrors !== null) {
      Object.keys(controlErrors).forEach(keyError => {
        errors.push({
          control_name: key,
          error_name: keyError,
          error_value: controlErrors[ keyError ]
        });
      });
    }
  });
  return errors;
}

Am Beispiel :

if (!this.formValid()) {
  const error: AllValidationErrors = getFormValidationErrors(this.regForm.controls).shift();
  if (error) {
    let text;
    switch (error.error_name) {
      case 'required': text = `${error.control_name} is required!`; break;
      case 'pattern': text = `${error.control_name} has wrong pattern!`; break;
      case 'email': text = `${error.control_name} has wrong email format!`; break;
      case 'minlength': text = `${error.control_name} has wrong length! Required length: ${error.error_value.requiredLength}`; break;
      case 'areEqual': text = `${error.control_name} must be equal!`; break;
      default: text = `${error.control_name}: ${error.error_name}: ${error.error_value}`;
    }
    this.error = text;
  }
  return;
}

1
Winkel 5 change - const controlErrors: ValidationErrors = form.controls [key] .errors;
Kris Kilton

Vorschlag zu prüfen , für truthy auf , controlErrors dh if (controlErrors) {die Überprüfung für nur nulleinen Fehler geben , wenn Fehler sindundefined
mtholen

8

Dies ist eine weitere Variante, die die Fehler rekursiv sammelt und nicht von einer externen Bibliothek wie lodash(nur ES6) abhängt :

function isFormGroup(control: AbstractControl): control is FormGroup {
  return !!(<FormGroup>control).controls;
}

function collectErrors(control: AbstractControl): any | null {
  if (isFormGroup(control)) {
    return Object.entries(control.controls)
      .reduce(
        (acc, [key, childControl]) => {
          const childErrors = collectErrors(childControl);
          if (childErrors) {
            acc = {...acc, [key]: childErrors};
          }
          return acc;
        },
        null
      );
  } else {
    return control.errors;
  }
}

6

Rekursive Methode zum Abrufen aller Fehler aus einem Angular-Formular . Nach dem Erstellen einer Formelstruktur gibt es keine Möglichkeit, alle Fehler aus dem Formular abzurufen. Dies ist sehr nützlich für Debugging-Zwecke, aber auch zum Zeichnen dieser Fehler.

Getestet auf Winkel 9

getFormErrors(form: AbstractControl) {
    if (form instanceof FormControl) {
        // Return FormControl errors or null
        return form.errors ?? null;
    }
    if (form instanceof FormGroup) {
        const groupErrors = form.errors;
        // Form group can contain errors itself, in that case add'em
        const formErrors = groupErrors ? {groupErrors} : {};
        Object.keys(form.controls).forEach(key => {
            // Recursive call of the FormGroup fields
            const error = this.getFormErrors(form.get(key));
            if (error !== null) {
                // Only add error if not null
                formErrors[key] = error;
            }
        });
        // Return FormGroup errors or null
        return Object.keys(formErrors).length > 0 ? formErrors : null;
    }
}

Ich verwende Angular 7 und habe zwei Änderungen an Ihrem Code vorgenommen: form.errors ?? nullIch musste das ?? damit es kompiliert. Noch wichtiger ist, dass ich in der FormGroup-Prüfbedingung hinzugefügt habe, || formParameter instanceof FormArraywas meine Anwendung wirklich geöffnet hat. Vielen Dank!
Tyler Forsythe

6

Oder Sie können diese Bibliothek einfach verwenden, um alle Fehler zu erhalten, auch von tiefen und dynamischen Formularen.

npm i @naologic/forms

Wenn Sie die statische Funktion in Ihren eigenen Formularen verwenden möchten

import {NaoFormStatic} from '@naologic/forms';
...
const errorsFlat = NaoFormStatic.getAllErrorsFlat(fg); 
console.log(errorsFlat);

Wenn Sie verwenden möchten, können NaoFromGroupSie es importieren und verwenden

import {NaoFormGroup, NaoFormControl, NaoValidators} from '@naologic/forms';
...
    this.naoFormGroup = new NaoFormGroup({
      firstName: new NaoFormControl('John'),
      lastName: new NaoFormControl('Doe'),
      ssn: new NaoFormControl('000 00 0000', NaoValidators.isSSN()),
    });

   const getFormErrors = this.naoFormGroup.getAllErrors();
   console.log(getFormErrors);
   // --> {first: {ok: false, isSSN: false, actualValue: "000 00 0000"}}

Lesen Sie die vollständige Dokumentation


2

Basierend auf der @ MixerOID- Antwort ist hier meine endgültige Lösung als Komponente (möglicherweise erstelle ich eine Bibliothek). Ich unterstütze auch FormArrays:

import {Component, ElementRef, Input, OnInit} from '@angular/core';
import {FormArray, FormGroup, ValidationErrors} from '@angular/forms';
import {TranslateService} from '@ngx-translate/core';

interface AllValidationErrors {
  controlName: string;
  errorName: string;
  errorValue: any;
}

@Component({
  selector: 'app-form-errors',
  templateUrl: './form-errors.component.html',
  styleUrls: ['./form-errors.component.scss']
})
export class FormErrorsComponent implements OnInit {

  @Input() form: FormGroup;
  @Input() formRef: ElementRef;
  @Input() messages: Array<any>;

  private errors: AllValidationErrors[];

  constructor(
    private translateService: TranslateService
  ) {
    this.errors = [];
    this.messages = [];
  }

  ngOnInit() {
    this.form.valueChanges.subscribe(() => {
      this.errors = [];
      this.calculateErrors(this.form);
    });

    this.calculateErrors(this.form);
  }

  calculateErrors(form: FormGroup | FormArray) {
    Object.keys(form.controls).forEach(field => {
      const control = form.get(field);
      if (control instanceof FormGroup || control instanceof FormArray) {
        this.errors = this.errors.concat(this.calculateErrors(control));
        return;
      }

      const controlErrors: ValidationErrors = control.errors;
      if (controlErrors !== null) {
        Object.keys(controlErrors).forEach(keyError => {
          this.errors.push({
            controlName: field,
            errorName: keyError,
            errorValue: controlErrors[keyError]
          });
        });
      }
    });

    // This removes duplicates
    this.errors = this.errors.filter((error, index, self) => self.findIndex(t => {
      return t.controlName === error.controlName && t.errorName === error.errorName;
    }) === index);
    return this.errors;
  }

  getErrorMessage(error) {
    switch (error.errorName) {
      case 'required':
        return this.translateService.instant('mustFill') + ' ' + this.messages[error.controlName];
      default:
        return 'unknown error ' + error.errorName;
    }
  }
}

Und der HTML:

<div *ngIf="formRef.submitted">
  <div *ngFor="let error of errors" class="text-danger">
    {{getErrorMessage(error)}}
  </div>
</div>

Verwendung:

<app-form-errors [form]="languageForm"
                 [formRef]="formRef"
                 [messages]="{language: 'Language'}">
</app-form-errors>

2

Versuchen Sie dies, es wird die Validierung für alle Steuerelemente in folgender Form aufgerufen:

validateAllFormControl(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.validateAllFormControl(control);            
    }
  });
}

1
export class GenericValidator {
    constructor(private validationMessages: { [key: string]: { [key: string]: string } }) {
    }

processMessages(container: FormGroup): { [key: string]: string } {
    const messages = {};
    for (const controlKey in container.controls) {
        if (container.controls.hasOwnProperty(controlKey)) {
            const c = container.controls[controlKey];
            if (c instanceof FormGroup) {
                const childMessages = this.processMessages(c);
                // handling formGroup errors messages
                const formGroupErrors = {};
                if (this.validationMessages[controlKey]) {
                    formGroupErrors[controlKey] = '';
                    if (c.errors) {
                        Object.keys(c.errors).map((messageKey) => {
                            if (this.validationMessages[controlKey][messageKey]) {
                                formGroupErrors[controlKey] += this.validationMessages[controlKey][messageKey] + ' ';
                            }
                        })
                    }
                }
                Object.assign(messages, childMessages, formGroupErrors);
            } else {
                // handling control fields errors messages
                if (this.validationMessages[controlKey]) {
                    messages[controlKey] = '';
                    if ((c.dirty || c.touched) && c.errors) {
                        Object.keys(c.errors).map((messageKey) => {
                            if (this.validationMessages[controlKey][messageKey]) {
                                messages[controlKey] += this.validationMessages[controlKey][messageKey] + ' ';
                            }
                        })
                    }
                }
            }
        }
    }
    return messages;
}
}

Ich habe es von Deborahk genommen und ein wenig modifiziert.


1
// IF not populated correctly - you could get aggregated FormGroup errors object
let getErrors = (formGroup: FormGroup, errors: any = {}) {
  Object.keys(formGroup.controls).forEach(field => {
    const control = formGroup.get(field);
    if (control instanceof FormControl) {
      errors[field] = control.errors;
    } else if (control instanceof FormGroup) {
      errors[field] = this.getErrors(control);
    }
  });
  return errors;
}

// Calling it:
let formErrors = getErrors(this.form);

0

Sie können die Eigenschaft this.form.errors durchlaufen.


14
Ich denke, das this.form.errorsgibt nur Validierungsfehler für das zurück this.form, nicht für this.form.controls. Sie können FormGroups und seine untergeordneten Elemente (beliebige Anzahl von FormGroups, FormControls und FormArrays) separat überprüfen. Um alle Fehler abzurufen, müssen Sie sie meiner Meinung nach rekursiv abfragen.
Risto Välimäki

0

Bei einem großen FormGroup-Baum können Sie lodash verwenden, um den Baum zu bereinigen und einen Baum nur der Steuerelemente mit Fehlern abzurufen. Dies erfolgt durch Wiederholen durch untergeordnete Steuerelemente (z. B. Verwenden allErrors(formGroup)) und Bereinigen aller vollständig gültigen Untergruppen von Steuerelementen:

private isFormGroup(control: AbstractControl): control is FormGroup {
  return !!(<FormGroup>control).controls;
}

// Returns a tree of any errors in control and children of control
allErrors(control: AbstractControl): any {
  if (this.isFormGroup(control)) {
    const childErrors = _.mapValues(control.controls, (childControl) => {
      return this.allErrors(childControl);
    });

    const pruned = _.omitBy(childErrors, _.isEmpty);
    return _.isEmpty(pruned) ? null : pruned;
  } else {
    return control.errors;
  }
}

-2

Ich verwende Winkel 5 und Sie können einfach die Statuseigenschaft Ihres Formulars mit FormGroup überprüfen, z

this.form = new FormGroup({
      firstName: new FormControl('', [Validators.required, validateName]),
      lastName: new FormControl('', [Validators.required, validateName]),
      email: new FormControl('', [Validators.required, validateEmail]),
      dob: new FormControl('', [Validators.required, validateDate])
    });

this.form.status wäre "UNGÜLTIG", wenn nicht alle Felder alle Validierungsregeln erfüllen.

Das Beste daran ist, dass Änderungen in Echtzeit erkannt werden.


1
Ja, aber wir müssen die Fehler einer ganzen Formulargruppe ermitteln und nicht nur wissen, ob sie nicht gültig ist
Motassem MK

Das OP benötigt die Validierungsnachrichten, die nicht in der Statuseigenschaft enthalten sind, da es sich nur um einen Booleschen Wert handelt.
Stefan
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.