Uzyskaj wszystkie błędy walidacji z Angular 2 FormGroup

90

Biorąc pod uwagę ten kod:

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

Jak mogę uzyskać wszystkie błędy walidacji z this.form?

Piszę testy jednostkowe i chcę uwzględnić rzeczywiste błędy walidacji w komunikacie potwierdzenia.

EagleBeak
źródło
Zamiast Validators.pattern ('true') możesz / powinieneś użyć Validators.requiredTrue, aby wymusić zaznaczenie pola wyboru.
Nieważne

Odpowiedzi:

141

Spotkałem ten sam problem i aby znaleźć wszystkie błędy walidacji i wyświetlić je napisałem następną metodę:

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]);
        });
      }
    });
  }

Nazwa formularza productFormpowinna zostać zmieniona na twoją.

Działa to w następujący sposób: wszystkie nasze kontrolki pobieramy z formularza w formacie {[p: string]: AbstractControl}i iterujemy po każdym kluczu błędu, aby uzyskać szczegółowe informacje o błędzie. Pomija nullwartości błędów.

Można go również zmienić, aby wyświetlać błędy walidacji w widoku szablonu, po prostu zamień console.log(..)na to, czego potrzebujesz.

Alex Efimov
źródło
2
Jak rozszerzyć powyższą metodę dla FormArray w tym samym wzorze?
Mohammad Sharaf Ali
Czy miałeś na myśli ' + controlErrors[keyErrors];zamiast ', controlErrors[keyErrors];?
ryanm
@ryanm nie, drukowanie jest inne jak obiekt lub jak wartość ciągu.
Alex Efimov
skąd mogę importować ValidationErrorsw Angular 2?
sainu
import { ValidationErrors } from '@angular/forms';
Craig Wayne
31

To rozwiązanie ze FormGroupwspornikami wewnętrznymi ( jak tutaj )

Testowano na: kątowe 4.3.6

get-form-validation-errors.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;
}

Na przykładzie :

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
źródło
1
Zmiana w Angular 5 - const controlErrors: ValidationErrors = form.controls [klucz] .errors;
Kris Kilton,
Sugestia, aby sprawdzić, czy jest prawda, controlErrors tj. if (controlErrors) {Jako sprawdzanie tylko pod kątem nullspowoduje błąd, jeśli wystąpią błędyundefined
mtholen
8

Jest to kolejny wariant, który zbiera błędy rekurencyjnie i nie zależy od żadnej zewnętrznej biblioteki, takiej jak lodash(tylko ES6):

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
źródło
6

Rekurencyjny sposób pobierania wszystkich błędów z formularza Angular , po utworzeniu dowolnej struktury formułowej nie ma możliwości odzyskania wszystkich błędów z formularza. Jest to bardzo przydatne do celów debugowania, ale także do wykreślania tych błędów.

Przetestowano pod kątem Angular 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
źródło
Używam Angular 7 i wprowadziłem dwie modyfikacje w twoim kodzie: form.errors ?? nullmusiałem usunąć ?? do kompilacji. Co ważniejsze, w warunku sprawdzania FormGroup dodałem, || formParameter instanceof FormArrayco naprawdę otworzyło moją aplikację. Dzięki!
Tyler Forsythe
6

Lub możesz po prostu użyć tej biblioteki, aby uzyskać wszystkie błędy, nawet z głębokich i dynamicznych formularzy.

npm i @naologic/forms

Jeśli chcesz używać funkcji statycznej we własnych formularzach

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

Jeśli chcesz użyć, NaoFromGroupmożesz go zaimportować i używać

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"}}

Przeczytaj pełną dokumentację

Pian0_M4n
źródło
2

Na podstawie odpowiedzi @MixerOID , oto moje ostateczne rozwiązanie jako komponent (może utworzę bibliotekę). Wspieram również FormArray:

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;
    }
  }
}

A HTML:

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

Stosowanie:

<app-form-errors [form]="languageForm"
                 [formRef]="formRef"
                 [messages]="{language: 'Language'}">
</app-form-errors>
ismaestro
źródło
2

Spróbuj tego, wywoła walidację dla całej kontroli w postaci:

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
źródło
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;
}
}

Wziąłem to od Deborahk i trochę zmodyfikowałem.

bangash
źródło
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);
uroslates
źródło
0

Możesz iterować po właściwości this.form.errors.

unsafePtr
źródło
14
Myślę, że this.form.errorszwraca to tylko błędy walidacji dla this.form, a nie dla this.form.controls. Możesz osobno zweryfikować FormGroups i jego elementy podrzędne (dowolną liczbę FormGroups, FormControls i FormArrays). Myślę, że aby pobrać wszystkie błędy, musisz zadawać je rekurencyjnie.
Risto Välimäki
0

W przypadku dużego drzewa FormGroup można użyć lodash, aby wyczyścić drzewo i uzyskać drzewo tylko formantów z błędami. Odbywa się to poprzez powtarzanie kontroli podrzędnych (np. Przy użyciu allErrors(formGroup)) i przycinanie wszelkich w pełni ważnych podgrup kontroli:

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
źródło
-2

Używam Angular 5 i możesz po prostu sprawdzić właściwość statusu swojego formularza za pomocą FormGroup np

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 miałoby wartość „INVALID”, chyba że wszystkie pola spełniają wszystkie reguły walidacji.

Najlepsze jest to, że wykrywa zmiany w czasie rzeczywistym.

Gagan
źródło
1
tak, ale musimy uzyskać błędy całej grupy, nie tylko wiedzieć, czy nie jest poprawna
Motassem MK
OP wymaga komunikatów walidacji, które nie są zawarte we właściwości status, ponieważ jest to tylko wartość logiczna.
Stefan,