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.

EagleBeak
quelle
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.

Alex Efimov
quelle
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;
}
MixerOID
quelle
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;
  }
}
Andreas Klöber
quelle
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;
    }
}
ArnauTG
quelle
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

Pian0_M4n
quelle
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>
ismaestro
quelle
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);            
    }
  });
}
Mayur Dongre
quelle
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.

Bangash
quelle
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);
Uroslate
quelle
0

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

unsafePtr
quelle
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;
  }
}
Olex Ponomarenko
quelle
-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.

Gagan
quelle
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