Obtenha todos os erros de validação do Angular 2 FormGroup

92

Dado este código:

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

Como posso obter todos os erros de validação de this.form?

Estou escrevendo testes de unidade e desejo incluir os erros de validação reais na mensagem de declaração.

EagleBeak
fonte
Em vez de Validators.pattern ('true'), você pode / deve usar Validators.requiredTrue para forçar a caixa de seleção sendo marcada.
Vazio em

Respostas:

146

Encontrei o mesmo problema e para encontrar todos os erros de validação e exibi-los, escrevi o próximo método:

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

O nome do formulário productFormdeve ser alterado para o seu.

Funciona da seguinte maneira: obtemos todos os nossos controles do formulário no formato {[p: string]: AbstractControl}e iteramos por cada chave de erro, para obter detalhes do erro. Ele pula os nullvalores de erro.

Ele também pode ser alterado para exibir erros de validação na visualização do modelo, basta substituir o console.log(..)que você precisa.

Alex Efimov
fonte
2
Como estender o método acima para FormArray no mesmo padrão?
Mohammad Sharaf Ali
Você quis dizer em ' + controlErrors[keyErrors];vez de ', controlErrors[keyErrors];?
ryanm
@ryanm não, há diferenças na impressão de um objeto semelhante ou de um valor de string semelhante.
Alex Efimov
de onde posso importar ValidationErrorsno angular 2?
sainu
import { ValidationErrors } from '@angular/forms';
Craig Wayne
31

Esta é a solução com FormGroupsuportes internos ( como aqui )

Testado em: Angular 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;
}

Usando o exemplo :

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
fonte
1
Alteração angular 5 - const controlErrors: ValidationErrors = form.controls [key] .errors;
Kris Kilton
Sugestão para verificar se há veracidade, controlErrors ou seja, if (controlErrors) {como verificar somente nulldará um erro se houver errosundefined
mtholen
8

Esta é outra variante que coleta os erros recursivamente e não depende de nenhuma biblioteca externa como lodash(ES6 apenas):

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
fonte
6

Forma recursiva de recuperar todos os erros de um formulário Angular , depois de criar qualquer tipo de estrutura de formulário não há como recuperar todos os erros do formulário. Isso é muito útil para fins de depuração, mas também para representar esses erros.

Testado para 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
fonte
Estou usando o Angular 7 e fiz duas modificações em seu código: form.errors ?? nullTive que remover o ?? para compilar. Mais importante, na condição de verificação FormGroup, adicionei o || formParameter instanceof FormArrayque realmente abriu meu aplicativo. Obrigado!
Tyler Forsythe
6

Ou você pode apenas usar esta biblioteca para obter todos os erros, mesmo de formulários profundos e dinâmicos.

npm i @naologic/forms

Se você quiser usar a função estática em seus próprios formulários

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

Se você quiser usar NaoFromGroupvocê pode importar e usar

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

Leia a documentação completa

Pian0_M4n
fonte
2

Com base na resposta @MixerOID , aqui está minha solução final como um componente (talvez eu crie uma biblioteca). Eu também suporte FormArray's:

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

E o HTML:

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

Uso:

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

Tente isto, ele chamará a validação para todos os controles no formulário:

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

Peguei de Deborahk e modifiquei um pouco.

bangash
fonte
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
fonte
0

Você pode iterar sobre a propriedade this.form.errors.

unsafePtr
fonte
14
Eu acho que this.form.errorsretorna apenas erros de validação para o this.form, não para this.form.controls. Você pode validar FormGroups e seus filhos (número arbitrário de FormGroups, FormControls e FormArrays) separadamente. Para buscar todos os erros, acho que você precisa perguntá-los recursivamente.
Risto Välimäki
0

Para uma árvore grande de FormGroup, você pode usar lodash para limpar a árvore e obter uma árvore apenas dos controles com erros. Isso é feito recorrendo a controles filhos (por exemplo, usando allErrors(formGroup)) e removendo quaisquer subgrupos de controles totalmente válidos:

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
fonte
-2

Estou usando o angular 5 e você pode simplesmente verificar a propriedade de status do seu formulário usando FormGroup, por exemplo

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 seria "INVALID" a menos que todos os campos passassem por todas as regras de validação.

A melhor parte é que ele detecta mudanças em tempo real.

Gagan
fonte
1
sim, mas precisamos obter os erros de um formgroup inteiro, não apenas saber se não é válido
Motassem MK
O OP precisa das mensagens de validação, que não estão incluídas na propriedade status, pois é apenas um booleano.
Stefan