从 Angular 2 FormGroup 获取所有验证错误

Posted

技术标签:

【中文标题】从 Angular 2 FormGroup 获取所有验证错误【英文标题】:Get all validation errors from 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') 来强制检查复选框。 【参考方案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');

【讨论】:

以上是关于从 Angular 2 FormGroup 获取所有验证错误的主要内容,如果未能解决你的问题,请参考以下文章

无法从'@angular/forms'导入“导入FormGroup,FormControl [重复]

typescript Angular Service将FormGroup与LocalStorage同步(获取和设置)

Angular FormGroup valueChanges 将属性转换为数组

Angular 2 FormGroup 添加验证器动态

formGroup的子组件内的Angular 2 formControlName

获取响应式表单FormGroup里的formControl对象示例