表单生成器组件已经被弃用。

54

我将我的项目迁移到angular 11,并注意到我添加的全局验证使得FormBuilder.group被标记为过时,提示如下:

group is deprecated: This api is not typesafe and can result in issues with Closure Compiler renaming.
Use the `FormBuilder#group` overload with `AbstractControlOptions` instead.

所以这个已不建议使用:

  ingredientForm = this.fb.group({
    ingredientType: ['', Validators.required],
    ingredientFlavor: [''],
    isMultiFlavor: [''],
    ingredientBrand: [''],
    ingredientName: [''],
    imageFile: ['']
  }, {validators: [ValidateThirdNumber.validate]});

没有 validators 选项它就不行。

我的 ValidateThirdNumber 验证器:

class ValidateThirdNumber {
  static validate(control: AbstractControl): void {
      if (control) {
      const isMultiFlavor = control.get('isMultiFlavor')?.value;
      const ingredientFlavor = control.get('ingredientFlavor')?.value;
      const ingredientBrand = control.get('ingredientBrand')?.value;
      const ingredientName = control.get('ingredientName')?.value;
      if (isMultiFlavor && ingredientFlavor.trim().length === 0) {
        control.get('ingredientFlavor')?.setErrors({required_if: true});
      } else {
        control.get('ingredientFlavor')?.setErrors(null);
      }
      if (!ingredientFlavor && !ingredientBrand && !ingredientName) {
        control.get('ingredientName')?.setErrors({required_at_least: true});
        control.get('ingredientFlavor')?.setErrors({required_at_least: true});
        control.get('ingredientBrand')?.setErrors({required_at_least: true});
      } else {
        control.get('ingredientName')?.setErrors(null);
        control.get('ingredientFlavor')?.setErrors(null);
        control.get('ingredientBrand')?.setErrors(null);
      }
      if (ingredientBrand && ingredientName && ingredientName === ingredientBrand) {
        control.get('ingredientName')?.setErrors({not_the_same: true});
        control.get('ingredientBrand')?.setErrors({not_the_same: true});
      }
    }
  }
}

我该如何使用AbstractControlOptions进行过载?


1
我认为你可以在这里找到解决方案,在这个部分中解释了弃用:https://angular.io/api/forms/FormBuilder - Ploppy
@Ploppy 我在发布这个问题之前已经阅读了文档,但不幸的是并没有帮助太多,我不知道该怎么做。 - ufk
5个回答

53

问题描述

文档中我们可以看到两个不同的使用group()函数的示例

group(controlsConfig: { [key: string]: any; }, options?: AbstractControlOptions): FormGroup

group(controlsConfig: { [key: string]: any; }, options: { [key: string]: any; }): FormGroup

第二个定义已被弃用(deprecated)

这两行代码之间的差异是 options?: AbstractControlOptionsoptions: { [key: string]: any; }

为了理解为什么 Angular 会抛出错误,现在我们将考虑AbstractControlOptions

interface AbstractControlOptions {
  validators?: ValidatorFn | ValidatorFn[] | null
  asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null
  updateOn?: 'change' | 'blur' | 'submit'
}

我们继续分解问题,注意到这个结构和您的结构之间的差别在于 ValidatorFn[]

interface ValidatorFn {
  (control: AbstractControl): ValidationErrors | null
}

在你的情况下,出现错误是因为你的Validator函数预期接受一个控件并返回ValidationErrors | null。在这行代码中validate(control: AbstractControl): void,你的代码实际上返回了void,但应该返回ValidationError | null

解决方案

根据问题描述,解决方案就是简单地修改ValidatorFn

确保你的ValidatorFn返回ValidationError或如果没有错误则返回nullValidationErrors defination

type ValidationErrors = {
    [key: string]: any;
};

您需要返回一个键值对对象,例如 {required_if: true}

我们可以通过添加预期的返回语句来更改您的代码

class ValidateThirdNumber {
  static validate(control: AbstractControl): ValidationErrors | null {
      if (control) {
      const isMultiFlavor = control.get('isMultiFlavor')?.value;
      const ingredientFlavor = control.get('ingredientFlavor')?.value;
      const ingredientBrand = control.get('ingredientBrand')?.value;
      const ingredientName = control.get('ingredientName')?.value;
      if (isMultiFlavor && ingredientFlavor.trim().length === 0) {
        control.get('ingredientFlavor')?.setErrors({required_if: true});
        return ({required_if: true});
      } else {
        control.get('ingredientFlavor')?.setErrors(null);
      }
      if (!ingredientFlavor && !ingredientBrand && !ingredientName) {
        control.get('ingredientName')?.setErrors({required_at_least: true});
        control.get('ingredientFlavor')?.setErrors({required_at_least: true});
        control.get('ingredientBrand')?.setErrors({required_at_least: true});
        return ({required_at_least: true});
      } else {
        control.get('ingredientName')?.setErrors(null);
        control.get('ingredientFlavor')?.setErrors(null);
        control.get('ingredientBrand')?.setErrors(null);
      }
      if (ingredientBrand && ingredientName && ingredientName === ingredientBrand) {
        control.get('ingredientName')?.setErrors({not_the_same: true});
        control.get('ingredientBrand')?.setErrors({not_the_same: true});
        return ({not_the_same: true});
      }
    }
    return null;
  }
}

如果自定义验证器具有复杂逻辑,无法在运行时确定setErrors块是否会运行怎么办?例如:something.forEach(ctl => {if xxx then setError, else ...}),在这种情况下应该期望返回什么? - Vincent

22

只需更改最后一行:

}, {validators: [ValidateThirdNumber.validate]});

}, {validators: [ValidateThirdNumber.validate]} as AbstractControlOptions);

注意: 确保AbstractControlOptions在花括号外面!

就是这样。


验证器应该使用大写字母还是小写字母“v”?我跟随您的建议使用小写字母,警告已经消失。另外,根据OvenKelvin的解决方案,我还实现了返回值和setErrors。 - learning...
这取决于您从自定义验证类返回的变量命名方式。如果您使用小写或大写命名,它是区分大小写的。 - Reza Taba
1
你,我的朋友,是一个英雄! - DJ Freeman
1
我喜欢这个简单的答案。它就是有效的。 - Javi

2

虽然Owen Kelvin的回答已经很详细了,但我想补充一下要点:

  1. 指定验证器函数时,请仅指定函数名称(即,不带任何参数)
  2. 该验证器函数必须接受一个且仅一个类型为AbstractControl的参数
  3. 验证器函数必须返回{key: value}null,但不是始终只返回null。当没有错误需要显示时,请返回null

这是在RegisterPage.ts中声明的表单示例:

import { Component, OnInit } from '@angular/core';

import {
  AbstractControl,
  FormBuilder,
  FormControl,
  FormGroup,
  Validators,
} from '@angular/forms';

@Component({
  selector: 'app-register',
  templateUrl: './register.page.html',
  styleUrls: ['./register.page.scss'],
})
export class RegisterPage implements OnInit {
  form: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.createForm();
  }

  createForm() {
    this.form = this.fb.group({
      matchEmail: this.fb.group(
        {
          //email textfield
          email: new FormControl(
            '',
            Validators.compose([
              Validators.required,
              Validators.maxLength(64),
              Validators.email,
            ])
          ),
          //confirm email textfield
          confirmEmail: new FormControl(''),
        },
        { validators: this.matchEmail }
      ),
    });
  }
}


验证函数将创建一个名为 mismatch 的新错误类型,并可插入到 RegisterPage.ts 文件中:
matchEmail(group: AbstractControl) {
    let email: string = group.get('email').value;
    let confirmEmail: string = group.get('confirmEmail').value;
    if (email.localeCompare(confirmEmail) != 0) {
        //error
        group.get('confirmEmail')?.setErrors({ mismatch: true });
        return { mismatch: true };
    } else {
        //no error, return null (don't return { mismatch: false })
        return null;
    }
}

您的HTML可能是这样的(您无法直接访问confirmEmail,它必须通过所属组matchEmail.confirmEmail进行访问):
<div *ngIf="form.get('matchEmail.confirmEmail').hasError('mismatch')
    && (form.get('matchEmail.confirmEmail').dirty ||
    form.get('matchEmail.confirmEmail').touched)">
    <small>Confirmation email doesn't match</small>
</div>

2
我曾经遇到过同样的问题,我的解决方案是这样的:
创建一个新变量,像这样:
const formOptions: AbstractControlOptions = {
   validators: your validator...,
   asyncValidators: your validators...
}

然后将此插入到您的表单构建器组中:

form: formGroup = this.fb.group({
  controls...
}, formOptions)

这将从我的代码中移除已弃用的警告。

希望能对您有所帮助。


2

我也遇到了同样的错误,我做了以下更改。

  • 确保您的验证器函数签名与此匹配。 (一个接收控件并同步返回验证错误映射(如果存在)的函数,否则为null。)

    • function Your_Function_Name(ObjectName: AbstractControl): ValidationErrors | null { }
  • 您可以像这样更改表单构建器中的代码。

    • const formOptions: AbstractControlOptions = { validators: Your_Function_Name };
  • 在表单构建器对象中像这样传递上述formOptions对象

    • this.formObject = this.formBuilder.group({ fullName: ['', [Validators.required]] }, formOptions);

嘿,你能否添加一个可用的代码示例吗? - noah lubko
@noahlubko,我已经用一个例子回答了这个问题。 - M. Al Jumaily

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接