【问题标题】:Linking two fields in an Angular validator在 Angular 验证器中链接两个字段
【发布时间】:2020-08-31 11:25:57
【问题描述】:

我有一个 Angular 9 表单,其中四个字段相关。一个是复选框,其余的是输入。当复选框被选中时,输入不应该是空的,但是当它没有被选中时,没有关系。我想为此制作验证器,以便仅当字段为空且第一个字段设置为 true 时才会出现错误。

我还考虑过创建一个表示复选标记状态的本地布尔值,然后像这样将其传递给验证器。

export function linkedFieldValidator(toggler: boolean): ValidatorFn {
  console.log('updated');
  return (control: AbstractControl): {[key: string]: any} | null => {
    return (toggler && control.value === '') ? {linkedField: {value: control.value}} : null;
  };
}

...
field: new FormControl('', linkedFieldValidator(this.checkboxvalue)),
...

但是,我想这不起作用,因为它只传递布尔值一次并且之后不会更新。即使调用updateValueAndValidity() 也不起作用,这对我来说很奇怪(如果不是这样,那么它的目的是什么?)。

我的FormGroup 的结构如下所示:

this.form = this.formBuilder.group({
  name: new FormControl(''), // don't care
  address: new FormControl(''), // don't care
  car: new FormControl(false), // do care - this is the checkmark
  license_plate: new FormControl('', Validators.pattern(MY_LICENSE_PLATE_REGEX)), // shouldn't be empty when car
  mileage: new FormControl('') // shouldn't be empty when car
  hair: new FormControl(false), // do care - this is the checkmark
  hair_color: new FormControl(''), // shouldn't be empty when hair
});

如您所见,我有几个FormControlls 通过彼此,我只希望它们中的几个被链接。另一个需要注意的重要事项是,虽然如果违反其中一个条件,整个表单可能会失效,但我希望能够单独解决每个错误,以便在适当的位置显示适当的消息。

我没有更多的想法,有人可以帮助我吗?我正在使用响应式表单。

【问题讨论】:

  • 你在使用响应式表单吗?
  • @Anton 对不起,我忘了说,是的,我是。

标签: angular angular9 angular-validation


【解决方案1】:

问题在于您只将初始值传递给 linkFieldValidator 函数。

为了动态获得值,您可以通过FormGroup 传递linkFieldValidator,如下所示:

readonly formGroup = this.formBuilder.group(
  {
    checkbox: '',
    name: ''
  },
  { validator: linkedFieldValidator }
);

完整样本:

import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
  AbstractControl,
  FormBuilder,
  FormGroup,
  ValidationErrors,
  ValidatorFn,
} from '@angular/forms';

export const linkedFieldValidator = (formGroup: FormGroup): ValidationErrors | null => {
  const [checkboxFormControlValue, nameFormControlValue] = [
    formGroup.get('checkbox')!.value,
    formGroup.get('name')!.value
  ];

  return checkboxFormControlValue && !nameFormControlValue
    ? { linkedField: { value: nameFormControlValue } }
    : null;
};

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'input-overview-example',
  styleUrls: ['input-overview-example.css'],
  templateUrl: 'input-overview-example.html'
})
export class InputOverviewExample {
  readonly formGroup = this.formBuilder.group(
    {
      checkbox: '',
      name: ''
    },
    { validator: linkedFieldValidator }
  );

  constructor(private readonly formBuilder: FormBuilder) {}
}

DEMO


编辑 1: 如果您需要将错误驻留在每个表单控件中,您可以将 linkedFieldValidator 更改为:

export const linkedFieldValidator = (formGroup: FormGroup): null => {
  const { value: checkboxFormControlValue } = formGroup.get('checkbox')!;
  const inputFormControls = [
    formGroup.get('input1')!,
    formGroup.get('input2')!,
    formGroup.get('input3')!,
  ];

  inputFormControls.forEach(inputFormControl => {
    const { value } = inputFormControl;
    const errors = checkboxFormControlValue && !value ? { linkedField: { value } } : null;
    inputFormControl.setErrors(errors);
  });

  return null;
};

注意,如果需要保留其他错误,可能需要在setErrors之前进行一些处理。

DEMO

编辑 2

对于可以有多个链接字段的通用方法,您可以执行以下操作:

type LinkedFormControl = Record<string, string | readonly string[]>;

const arrayify = <T>(itemOrItems: T | readonly T[]): readonly T[] => {
  return Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
};

const getErrorObjectSanitized = <T extends object>(obj: T): T | null => {
  return Object.keys(obj).length === 0 ? null : obj;
};

const getErrorsFor = (
  checkerValue: boolean,
  formControl: FormControl,
): object | null => {
  const { errors, value } = formControl;
  const { error, ...oldErrors } = errors || {};
  const processedErrors = {
    ...(checkerValue && !value ? { error: true } : {}),
    ...oldErrors,
  };

  return getErrorObjectSanitized(processedErrors);
};

export const linkedFieldValidator = (linkedFormControls: LinkedFormControl) => {
  return (formGroup: FormGroup): ValidationErrors | null => {
    Object.keys(linkedFormControls).forEach(key => {
      const { value: checkerValue } = formGroup.get(key)!;
      const dependentKeys = arrayify(linkedFormControls[key]);

      dependentKeys
        .map(dependentKey => formGroup.get(dependentKey)!)
        .forEach((dependentFormControl: FormControl) => {
          dependentFormControl.setErrors(
            getErrorsFor(checkerValue, dependentFormControl),
          );
        });
    });

    return null;
  };
};

...调用会是这样的:

{
  validator: linkedFieldValidator({
    car: ['license_plate', 'mileage'],
    hair: 'hair_color',
  }),
},

DEMO

【讨论】:

  • 我已经看到了这种可能性,但正如我在帖子中提到的那样,这对我来说真的不起作用,因为我想将一个复选框“链接”到三个输入。据我所知,这会使整个FormGroup 无效,这不是我想要的;只有空的字段(选中复选框时)应该是无效的。
  • 也许您的帖子根本不清楚。你能创建一个堆栈闪电战来演示这个问题吗?
  • @Luctia "据我所知,这会使整个 FormGroup 无效,这不是我想要的;只有空的字段(选中复选框时)应该是无效。”我之前忘了引用你的句子,但值得说一下:如果FormGroup 中的AbstractControl 任何 无效,则FormGroup 无效。因此,您不能将特定的AbstractControl 标记为无效而不自动标记 FormGroup
  • 话虽这么说,我试图推断出你想用你的问题来完成什么,我想出了this。你能看一下吗?
  • 我认为该示例对我有用。我的小组中有不止一个这样的案例,但我认为我可以处理这些案例。关于您之前的评论的另一个注释,我想我有一段时间对这意味着什么有一个错误的想法,在解决这个问题的同时,我已经开始理解这意味着什么,你是对的,它可能是好用不同的说法。我将再看一下我的问题,如果您将提出的问题添加到答案中,我会将其标记为已回答。感谢您的帮助!
【解决方案2】:

编辑 1:

DEMO 在子表单(输入所在的位置)和主表单上进行验证

原答案:

您可以使用cross-fields validation 组合检查多个字段。在这里,我有 1 个复选框(初始状态为 false 或未选中)和 3 个输入字段。在我的自定义验证器中,我只检查值并设置相应的表单验证错误:

DEMO

export class ProfileEditorComponent {
  myForm = new FormGroup({
    'checker': new FormControl(false),
    'name': new FormControl(),
    'middleName': new FormControl(),
    'lastName': new FormControl()
}, { validators: myCustomValidator });

  constructor(private fb: FormBuilder) { }
}

export const myCustomValidator : ValidatorFn = (control: FormGroup): ValidationErrors | null => {
  const checker = control.get('checker').value;
  const name = control.get('name');
  if (checker) {
    if (name.value===null || name.value==="") {
      return {'firstNameMissing': true};
      // TODO: do the same for the other fields or any field combination
    } else {
      return null;
    }
  }
  return null;
};

然后错误信息的显示如下:

  <label>
    First Name:
    <input type="text" formControlName="name">
  </label>
  <div *ngIf="myForm.errors?.firstNameMissing && (myForm.touched || myForm.dirty)" class="cross-validation-error-message alert alert-danger">
    First name required.
  </div>

【讨论】:

  • 这个解决方案似乎让我非常接近某些东西,我想我只是缺少一件事 - 我如何在嵌套的 FormGroups 中使用它?一切似乎都正常,但在模板中,ngIf 似乎无法读取myForm.get('subFormGroup').errors...。我没有收到错误,但我可以在日志中看到该组抛出了firstNameMissing 错误,并且它没有触发ngIf。有什么建议吗?
  • @Luctia 因此,您希望仅在子组中执行验证。复选框位于何处 - 在主组还是在子组中?
  • 我在FormGroup 中用更好的结构表示更新了我的问题,我希望你能从中推断出我的意思。不过,我已经接受了一个答案,所以如果您认为当前的答案不正确,请花更多的时间来解决这个问题。感谢您的帮助!
  • 一切都很好,现在我得到了你想要实现的目标,我认为这两种解决方案都朝着同一个方向发展。祝你好运!
猜你喜欢
  • 2018-09-08
  • 1970-01-01
  • 2014-08-08
  • 1970-01-01
  • 1970-01-01
  • 2013-02-28
  • 2018-06-05
  • 1970-01-01
  • 2015-10-22
相关资源
最近更新 更多