【问题标题】:Get all validation errors from Angular 2 FormGroup从 Angular 2 FormGroup 获取所有验证错误
【发布时间】:2017-04-02 11:54:05
【问题描述】:

鉴于此代码:

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

如何从this.form 获取所有验证错误?

我正在编写单元测试并希望在断言消息中包含实际的验证错误。

【问题讨论】:

  • 您可以/应该使用 Validators.requiredTrue 代替 Validators.pattern('true') 来强制检查复选框。

标签: angular typescript validation


【解决方案1】:

您可以迭代 this.form.errors 属性。

【讨论】:

  • 我猜this.form.errors 只返回this.form 的验证错误,而不是this.form.controls。您可以分别验证 FormGroups 及其子项(任意数量的 FormGroups、FormControls 和 FormArrays)。要获取所有错误,我认为您需要递归地询问它们。
【解决方案2】:

我遇到了同样的问题,为了找到所有验证错误并显示它们,我写了这个方法:

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

表单名称productForm 应更改为您的表单实例名称。

它以这种方式工作:我们从格式为{[p: string]: AbstractControl} 的表单中获取所有控件,并按每个错误键进行迭代,以获取错误的详细信息。它会跳过 null 错误值。

也可以更改为在模板视图上显示验证错误,只需将console.log(..) 替换为您需要的即可。

【讨论】:

  • 如何将FormArray的上述方法扩展成相同的模式?
  • 您的意思是' + controlErrors[keyErrors]; 而不是', controlErrors[keyErrors];
  • 我可以从哪里导入 Angular 2 中的 ValidationErrors
  • import { ValidationErrors } from '@angular/forms';
  • 请注意这不适用于嵌套的 FormGroups,我建议使用 Mayur Dongre 的方法
【解决方案3】:
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;
}
}

我从Deborahk拿来并稍微修改了一下。

【讨论】:

    【解决方案4】:

    这是带有FormGroup 内部支持的解决方案 (like here)

    测试于: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;
    }
    

    使用示例

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

    【讨论】:

    • Angular 5 更改 - const controlErrors: ValidationErrors = form.controls[key].errors;
    • 建议检查controlErrors 的真实性,即if (controlErrors) {,因为如果错误为undefined,则仅检查null 会出错
    【解决方案5】:

    或者您可以只使用这个库来获取所有错误,即使是来自深层和动态表单。

    npm i @naologic/forms
    

    如果你想在自己的表单上使用静态函数

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

    如果你想使用NaoFromGroup你可以导入并使用它

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

    阅读full documentation

    【讨论】:

      【解决方案6】:
      // 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);
      

      【讨论】:

        【解决方案7】:

        我使用的是 Angular 5,您可以使用 FormGroup 例如简单地检查表单的状态属性

        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 将为“INVALID”。

        最好的部分是它可以实时检测变化。

        【讨论】:

        • 是的,但我们需要获取整个表单组的错误,而不仅仅是知道它是否无效
        • OP 需要验证消息,它不包含在 status 属性中,因为它只是一个布尔值。
        【解决方案8】:

        试试这个,它将为表单中的所有控件调用验证:

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

        【讨论】:

          【解决方案9】:

          对于大型 FormGroup 树,您可以使用 lodash 清理树并获得仅包含错误控件的树。这是通过重复子控件(例如使用allErrors(formGroup))并修剪任何完全有效的控件子组来完成的:

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

          【讨论】:

            【解决方案10】:

            这是另一种递归收集错误的变体,不依赖于任何外部库,如 lodash(仅限 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;
              }
            }
            

            【讨论】:

              【解决方案11】:

              基于@MixerOID 响应,这是我作为组件的最终解决方案(也许我创建了一个库)。我也支持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;
                  }
                }
              }
              

              还有 HTML:

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

              用法:

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

              【讨论】:

                【解决方案12】:

                从 Angular 表单中检索所有错误的递归方式,在创建任何类型的公式结构后,无法从表单中检索所有错误。这对于调试目的非常有用,而且对于绘制这些错误也非常有用。

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

                【讨论】:

                • 我正在使用 Angular 7 并对您的代码进行了两次修改:form.errors ?? null 我必须删除 ??让它编译。更重要的是,在 FormGroup 检查条件中,我添加了|| formParameter instanceof FormArray,它真正打开了我的应用程序。谢谢!
                • FormArray 怎么样?
                【解决方案13】:

                它可能关注的对象 - 我对 Andreas 代码进行了调整,以便将所有错误代码放在一个平面对象中,以便更轻松地记录可能出现的错误。

                请考虑:

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

                【讨论】:

                  【解决方案14】:
                  **I met the same problem and for finding all validation errors and 
                  displaying only first error, I wrote next method:**
                  
                  > first declare variable on top
                    public errors: any = [];
                    public fieldError: any = '';
                  
                  > now subscribe form on noOnInit 
                    
                    this.form.valueChanges.subscribe(() => {
                    this.showOnlyFirstError(this.form);
                    this.errors = []
                    });
                    this.showOnlyFirstError(this.form);
                  
                  > now call function
                  
                   showOnlyFirstError(form) {
                   Object.keys(form.controls).forEach(key => {
                  
                   const controlErrors: ValidationErrors = form.get(key).errors;
                   if (controlErrors != null) {
                        Object.keys(controlErrors).forEach(keyError => {
                          const showMessage = key + " is " + keyError
                          this.errors.push(showMessage)
                          this.fieldError = this.errors[0]
                        });
                       }
                     });
                   }
                  

                  【讨论】:

                    【解决方案15】:

                    我需要呈现包含 FormControls、FromGroups 和 FormArrays 的非常复杂的 FormGroup 控件的所有错误

                    我试图找到简单的解决方案,但我无法找到支持所有类型控件的完美解决方案,因此我开发了以下简单的递归函数,并与大家分享:

                    export interface FieldError {
                        formGroupName: string;
                        fieldName: string;
                        errorCode: string;
                    }
                    
                    export function getFormErrors(
                         control: AbstractControl, 
                         formGroupName: string, 
                         fieldName: string, 
                         errors: FieldError[]) {
                    
                         if (control instanceof FormGroup) {
                             Object.keys(control.controls).forEach(controlName => {
                                 let formControl = control.get(controlName);
                                 if (formControl) {
                                     let fGroupName = formGroupName + "-" + controlName;
                                     getFormErrors(formControl, fGroupName, controlName, errors);
                                 }
                             })
                         }
                    
                         if (control instanceof FormArray) {
                             control.controls.forEach((fControl: AbstractControl, index) => {
                                 let fGroupName = formGroupName + "-" + index;
                                 getFormErrors(fControl, fGroupName, "Array", errors);
                             })
                         }
                    
                         if (control instanceof FormControl) {
                             const controlErrors: ValidationErrors | null = control.errors;
                             if (controlErrors) {
                                 Object.keys(controlErrors).forEach(errorCode => {
                                     errors.push({
                                         formGroupName: formGroupName,
                                         fieldName: fieldName,
                                         errorCode: errorCode
                                     })
                                 });
                             }
                         }
                     }
                    

                    用法如下:

                        let errors: FieldError[] = []
                        getFormErrors(YOUR_FORM_GROUP, "root", "", errors);
                    

                    【讨论】:

                      【解决方案16】:

                      调整the accepted answer 以返回一个可以打印到控制台的字符串:

                      function getFormValidationErrors(form: FormGroup): string {
                          return Object.keys(form.controls)
                              .map((control) => {
                                  const controlErrors = form.get(control).errors;
                                  if (!controlErrors) {
                                      return [];
                                  }
                                  const controlErrorsString = Object.keys(controlErrors)
                                      .flatMap(
                                          (keyError) => `${keyError}: ${controlErrors[keyError]}`
                                      )
                                      .join(', ');
                                  return `${control}: {${controlErrorsString}}`;
                              })
                              .filter((list) => list.length > 0)
                              .join('\n');
                      }
                      

                      【讨论】:

                        猜你喜欢
                        • 2018-08-05
                        • 1970-01-01
                        • 2017-09-22
                        • 2015-08-09
                        • 2010-12-25
                        • 2017-04-15
                        • 1970-01-01
                        • 2021-11-17
                        • 1970-01-01
                        相关资源
                        最近更新 更多