Nhận tất cả các lỗi xác thực từ Angular 2 FormGroup


91

Đưa ra mã này:

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

Làm cách nào để lấy tất cả các lỗi xác thực this.form?

Tôi đang viết các bài kiểm tra đơn vị và muốn đưa các lỗi xác thực thực tế vào thông báo xác nhận.


Thay vì Validators.pattern ('true'), bạn có thể / nên sử dụng Validators.requiredTrue để thực thi hộp kiểm được chọn.
Void

Câu trả lời:


142

Tôi đã gặp vấn đề tương tự và để tìm tất cả các lỗi xác thực và hiển thị chúng, tôi đã viết phương pháp tiếp theo:

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

Tên biểu mẫu productFormnên được đổi thành của bạn.

Nó hoạt động theo cách tiếp theo: chúng tôi nhận tất cả các điều khiển của mình từ biểu mẫu ở định dạng {[p: string]: AbstractControl}và lặp lại theo từng khóa lỗi, để biết chi tiết về lỗi. Nó bỏ qua nullcác giá trị lỗi.

Nó cũng có thể được thay đổi để hiển thị lỗi xác thực trên chế độ xem mẫu, chỉ cần thay thế console.log(..)những gì bạn cần.


2
Làm thế nào để mở rộng phương thức trên cho FormArray trong cùng một mẫu?
Mohammad Sharaf Ali

Ý bạn là ' + controlErrors[keyErrors];thay vì ', controlErrors[keyErrors];?
ryanm

@ryanm không, có sự khác biệt trong việc in như đối tượng hoặc giá trị chuỗi.
Alex Efimov

Tôi có thể nhập từ đâu ValidationErrorstrong góc 2?
sainu

import { ValidationErrors } from '@angular/forms';
Craig Wayne

31

Đây là giải pháp có FormGrouphỗ trợ bên trong ( như ở đây )

Đã thử nghiệm trên: 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;
}

Sử dụng ví dụ :

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

1
Thay đổi góc 5 - const controlErrors: ValidationErrors = form.controls [key] .errors;
Kris Kilton

Đề xuất kiểm tra tính trung thực controlErrors tức if (controlErrors) {là chỉ kiểm tra nullsẽ đưa ra lỗi nếu có lỗiundefined
mtholen

8

Đây là một biến thể khác thu thập lỗi một cách đệ quy và không phụ thuộc vào bất kỳ thư viện bên ngoài nào như lodash(chỉ dành cho 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;
  }
}

6

Cách đệ quy để truy xuất tất cả các lỗi từ biểu mẫu Angular , sau khi tạo bất kỳ loại cấu trúc danh mục nào, không có cách nào để truy xuất tất cả các lỗi từ biểu mẫu. Điều này rất hữu ích cho mục đích gỡ lỗi nhưng cũng để vẽ các lỗi đó.

Đã thử nghiệm cho 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;
    }
}

Tôi đang sử dụng Angular 7 và đã thực hiện hai sửa đổi đối với mã của bạn: form.errors ?? nullTôi phải xóa dấu ?? để nó biên dịch. Quan trọng hơn, trong điều kiện kiểm tra FormGroup, tôi đã thêm điều kiện || formParameter instanceof FormArraythực sự mở ra ứng dụng của tôi. Cảm ơn!
Tyler Forsythe

6

Hoặc bạn chỉ có thể sử dụng thư viện này để nhận tất cả các lỗi, ngay cả từ các biểu mẫu sâu và động.

npm i @naologic/forms

Nếu bạn muốn sử dụng hàm tĩnh trên các biểu mẫu của riêng mình

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

Nếu bạn muốn sử dụng, NaoFromGroupbạn có thể nhập và sử dụng nó

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

Đọc tài liệu đầy đủ


2

Dựa trên phản hồi @MixerOID , đây là giải pháp cuối cùng của tôi với tư cách là một thành phần (có thể tôi tạo một thư viện). Tôi cũng hỗ trợ 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;
    }
  }
}

Và HTML:

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

Sử dụng:

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

2

Hãy thử Điều này, nó sẽ gọi xác thực cho tất cả các điều khiển ở dạng:

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

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

Tôi lấy nó từ Deborahk và sửa đổi nó một chút.


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

0

Bạn có thể lặp qua thuộc tính this.form.errors.


14
Tôi đoán rằng điều đó this.form.errorschỉ trả về lỗi xác thực cho this.form, không phải cho this.form.controls. Bạn có thể xác thực FormGroups và con của nó (số lượng FormGroups, FormControls và FormArrays tùy ý) một cách riêng biệt. Để tìm nạp tất cả các lỗi, tôi nghĩ bạn cần phải hỏi chúng một cách đệ quy.
Risto Välimäki vào

0

Đối với một cây FormGroup lớn, bạn có thể sử dụng lodash để dọn dẹp cây và lấy một cây chỉ gồm các điều khiển có lỗi. Điều này được thực hiện bằng cách lặp lại thông qua các kiểm soát con (ví dụ: sử dụng allErrors(formGroup)) và lược bỏ bất kỳ nhóm kiểm soát phụ nào đầy đủ hợp lệ:

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

-2

Tôi đang sử dụng angle 5 và bạn có thể chỉ cần kiểm tra thuộc tính trạng thái của biểu mẫu bằng cách sử dụng FormGroup, ví dụ:

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 sẽ là "INVALID" trừ khi tất cả các trường vượt qua tất cả các quy tắc xác thực.

Phần tốt nhất là nó phát hiện các thay đổi trong thời gian thực.


1
vâng nhưng chúng ta cần phải nhận được các lỗi của toàn bộ một formgroup, không chỉ biết nếu không có giá trị của nó
Motassem MK

OP cần các thông báo xác thực, không được bao gồm trong thuộc tính trạng thái, vì nó chỉ là một boolean.
Stefan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.