如何在 Angular(v2 起)反应形式中找到无效控件

How to find the invalid controls in Angular(v2 onwards) reactive form

我在 Angular 中有一个反应形式,如下所示:

this.AddCustomerForm = this.formBuilder.group({
    Firstname: ['', Validators.required],
    Lastname: ['', Validators.required],
    Email: ['', Validators.required, Validators.pattern(this.EMAIL_REGEX)],
    Picture: [''],
    Username: ['', Validators.required],
    Password: ['', Validators.required],
    Address: ['', Validators.required],
    Postcode: ['', Validators.required],
    City: ['', Validators.required],
    Country: ['', Validators.required]
});

createCustomer(currentCustomer: Customer) 
{
    if (!this.AddCustomerForm.valid)
    {
        //some app logic
    }
}

this.AddCustomerForm.valid returns 错误,但一切看起来都很好。

我试图通过检查控件集合中的状态 属性 来查找。但是我想知道有没有办法找到无效的并显示给用户?

您可以简单地遍历每个控件并检查状态:

    public findInvalidControls() {
        const invalid = [];
        const controls = this.AddCustomerForm.controls;
        for (const name in controls) {
            if (controls[name].invalid) {
                invalid.push(name);
            }
        }
        return invalid;
    }

窗体和所有控件都扩展了 angular class AbstractControl。每个实现都有一个验证错误的访问器。

let errors = this.AddCustomerForm.errors
// errors is an instance of ValidatorErrors

api 文档包含所有参考资料 https://angular.io/api/forms/AbstractControl

编辑

我认为错误访问器是这样工作的,但是这个 link 到 github 表明还有其他一些人和我有同样的想法 https://github.com/angular/angular/issues/11530

在任何情况下,通过使用控件访问器,您都可以遍历表单中的所有 formControl。

Object.keys(this.AddCustomerForm.controls)
    .forEach( control => {
        //check each control here
        // if the child is a formGroup or a formArray
        // you may cast it and check it's subcontrols too
     })

如果表单中的字段不多,只需按 F12 并将鼠标悬停在控件上,您将能够看到带有字段 pristine/touched/valid 值的弹出窗口- “#fieldname.form-control.ng-untouched.ng-无效”。

我冒昧地改进了 AngularInDepth.com-s 代码,以便它也可以递归搜索嵌套表单中的无效输入。它是由 FormArray-s 还是 FormGroup-s 嵌套的。只需输入顶级 formGroup,它将 return 所有无效的 FormControl。

如果您将 FormControl 检查和添加到无效数组功能分开到一个单独的函数中,您可能会略过一些 "instanceof" 类型检查。这将使函数看起来更清晰,但我需要一个全局的单一函数选项来获取所有无效 formControl 的平面数组,这就是解决方案!

findInvalidControls( _input: AbstractControl, _invalidControls: AbstractControl[] ): AbstractControl[] {
    if ( ! _invalidControls ) _invalidControls = [];
    if ( _input instanceof FormControl  ) {
        if ( _input.invalid ) _invalidControls.push( _input );
        return _invalidControls;
    }

    if ( ! (_input instanceof FormArray) && ! (_input instanceof FormGroup) ) return _invalidControls;

    const controls = _input.controls;
    for (const name in controls) {
        let control = controls[name];
        switch( control.constructor.name )
        {
            case 'AbstractControl':
            case 'FormControl':
                if (control.invalid) _invalidControls.push( control );
                break;

            case 'FormArray':
                (<FormArray> control ).controls.forEach( _control => _invalidControls = findInvalidControls( _control, _invalidControls ) );
                break;

            case 'FormGroup':
                _invalidControls = findInvalidControls( control, _invalidControls );
                break;
        }
    }

    return _invalidControls;
}

仅供需要的人使用,因此他们不必自己编写代码。

编辑 #1

有人要求它也 return 无效的 FormArray-s 和 FormGroups,所以如果您也需要它,请使用此代码

findInvalidControls( _input: AbstractControl, _invalidControls: AbstractControl[] ): AbstractControl[] {
    if ( ! _invalidControls ) _invalidControls = [];
    if ( _input instanceof FormControl  ) {
        if ( _input.invalid ) _invalidControls.push( _input );
        return _invalidControls;
    }

    if ( ! (_input instanceof FormArray) && ! (_input instanceof FormGroup) ) return _invalidControls;

    const controls = _input.controls;
    for (const name in controls) {
        let control = controls[name];
        if (control.invalid) _invalidControls.push( control );
        switch( control.constructor.name )
        {    
            case 'FormArray':
                (<FormArray> control ).controls.forEach( _control => _invalidControls = findInvalidControls( _control, _invalidControls ) );
                break;

            case 'FormGroup':
                _invalidControls = findInvalidControls( control, _invalidControls );
                break;
        }
    }

    return _invalidControls;
}

我刚刚解决了这个问题:每个表单字段都有效,但表单本身仍然无效。

原来我在 FormArray 上设置了 'Validator.required',其中控件是动态的 added/removed。因此,即使 FormArray 为空,它仍然是必需的,因此表单始终无效,即使每个可见控件都已正确填充。

我没有找到表单的无效部分,因为我的 'findInvalidControls' 函数只检查了 FormControl 而没有 FormGroup/FormArray。所以我更新了一下:

/* 
   Returns an array of invalid control/group names, or a zero-length array if 
   no invalid controls/groups where found 
*/
public findInvalidControlsRecursive(formToInvestigate:FormGroup|FormArray):string[] {
    var invalidControls:string[] = [];
    let recursiveFunc = (form:FormGroup|FormArray) => {
      Object.keys(form.controls).forEach(field => { 
        const control = form.get(field);
        if (control.invalid) invalidControls.push(field);
        if (control instanceof FormGroup) {
          recursiveFunc(control);
        } else if (control instanceof FormArray) {
          recursiveFunc(control);
        }        
      });
    }
    recursiveFunc(formToInvestigate);
    return invalidControls;
  }

您可以记录表单 console.log(this.addCustomerForm.value) 的值,它将控制所有控件的值,然后 null 或“”(空)字段表示无效控件

无效的 Angular 控件具有名为 'ng-invalid'.

的 CSS class

在 Chrome 的 DevTools 下,select 控制台选项卡。

在控制台提示符 运行 中,以下命令是为了获取带有 CSS class 'ng-invalid'[=15= 的无效 Angular 控件]

document.getElementsByClassName('ng-invalid')

输出应该与此类似:

本例中,带下划线的文字为表单控件listen-address,圆圈文字:.ng-invalid表示该控件无效

Note: Tested in chrome

我认为您应该尝试使用 this.form.updateValueAndValidity() 或尝试在每个控件中执行相同的方法。

试试这个

 findInvalidControls(f: FormGroup) {
    const invalid = [];
    const controls = f.controls;
    for (const name in controls) {
      if (controls[name].invalid) {
        invalid.push(name);
      }
    }
    return invalid;
  }

现在,在 angular9 中,您可以使用 markAllAsTouched() 方法来显示无效控件验证器:

this.AddCustomerForm.markAllAsTouched();

反应形式的每个控件上都存在一个 .error 属性。如果此 .error 设置为真,则表示控件无效。因此,遍历控件并检查此 .error 字段将使我们知道哪些字段/控件无效。

下面的代码将记录所有无效的控件

for (let el in this.ReactiveForm.controls) {
      if (this.ReactiveForm.controls[el].errors) {
        console.log(el)
      }
 }          

也可以将字段名称附加到数组或字符串中,并指示用户哪些字段无效

检查 html 页面中的空或空表单控件值

表单控件值:{{formname.value | json}}

解决上述问题的更清晰且不可变的递归版本:

P.S: 您将需要两种方法。

Working tested uptill Angular 11

In case compiler complains about flatMap, refer to this(), and don't forger to restart ng serve

findInvalidControls(controls = this.defaultFormGroup.controls) {
    const ctrls = Object.values(controls);
    const names = Object.keys(controls);
    return ctrls.map((a,i) => [a, i])
      .filter(a => (a[0] as FormControl).invalid)
      .flatMap(a => {
        if (a[0] instanceof FormArray) {
          return this.findInvalidArrayControls(a[0].controls);
        } else if (a[0] instanceof FormGroup) {
          return this.findInvalidControls(a[0].controls);
        } else {
          return names[a[1] as number];
        }
      });
  }

  findInvalidArrayControls(controls: AbstractControl[]) {
    const ctrls = Object.values(controls);
    const names = Object.keys(controls);
    return ctrls.map((a,i) => [a, i])
      .filter(a => (a[0] as FormControl).invalid)
      .flatMap(a => {
        if (a[0] instanceof FormArray) {
          return this.findInvalidArrayControls(a[0].controls);
        } else if (a[0] instanceof FormGroup) {
          return this.findInvalidControls(a[0].controls);
        }
         else {
          return names[a[1] as number];
        }
     });
  }

就我而言,我禁用了所有表单控件。

它似乎是 Angular 中的一个未解决的错误:https://github.com/angular/angular/issues/39287

所以我也反了这条龙。 就像我这个勇敢的骑士一样,我先聚集了my weapons, read the maps,然后与这只可怕的野兽战斗。

备注

对于复杂的形式或结构,这不是一个可接受的答案,但我发现它适用于没有太多复杂性的简单形式

代码执行以下操作:

  • 获取表单控件作为数组
  • 循环检查表单控件是否无效
  • 如果无效,过滤器将包含它
  • 如果有任何无效,结果数组将填充该控件
  • 如果此结果的长度等于 0,我们可以声明没有控件无效并且整个表单有效
isFormValid = () :boolean => 
    Object.values(this.form.controls)
        .filter(c => c.invalid).length === 0

// or single lined
isFormValid = () :boolean => Object.values(this.form.controls).filter(c => c.invalid).length === 0

您可以在您想要的地方使用它,在提交按钮上、onSubmit 或您自己的最佳位置。

创建旗帜

inProcess: boolean= false
   
   this.AddCustomerForm = this.formBuilder.group({
       Firstname: ['', Validators.required],
       Lastname: ['', Validators.required],
       Username: ['', Validators.required],
       Password: ['', Validators.required],
       Address: ['', Validators.required],
       Postcode: ['', Validators.required],
       City: ['', Validators.required],
       Country: ['', Validators.required]
   });

onSubmit()
{
   if(this.AddCustomerForm.invalid)
   {
     return
   }

  this.inProcess = true

// pass form value to restapi

}

以及您在 HTML 表单禁用按钮中使用的这个 inProcess 标志

<button mat-button [disable]="inProcess"> ADD </button>

一旦所有表单值都正确,则只有添加按钮可见

希望对大家有所帮助!!!

对于任何需要过滤 formArray 的人来说,只有有效的控件才是解决方案;

const validControls = this.participantsDetails.controls.filter(control => control.valid);

当然还有无效的;

const validControls = this.participantsDetails.controls.filter(control => control.invalid);