【问题标题】:Angular forms require one of two form groups to be validAngular 表单需要两个表单组之一才能有效
【发布时间】:2022-11-11 14:15:57
【问题描述】:

我正在尝试实现必须输入 A 或 B 的反应角度形式。 A 是一个唯一的 id,B 是一组标识该 id 的值。现在我尝试验证一个有效的表单,如果输入 A 或输入 B,包括所有必需的值。我找到了几个基于 FormFields 实现此行为的解决方案,但无法使其与值组一起工作。

<form class="container" [formGroup]="myForm" (ngSubmit)="onSubmit()">
      <mat-form-field class="w-1/2">
        <mat-label>ID</mat-label>
        <input matInput type="number" formControlName="id">
      </mat-form-field>

      <div class="grid grid-cols-3 gap-4" formGroupName="innerGroup">
        <mat-form-field>
          <mat-label>First Name</mat-label>
          <input matInput type="number" formControlName="firstName">
        </mat-form-field>

        <mat-form-field>
          <mat-label>Last Name</mat-label>
          <input matInput type="number" formControlName="lastName">
        </mat-form-field>
      </div>
</form>

我的第一个想法是覆盖表单的默认验证器,但我不知道该怎么做。甚至不确定这是否可能。我试图调整https://stackoverflow.com/a/48714721 以在我的场景中工作,但由于内部表单组的额外复杂性,我不知道如何让它工作。

【问题讨论】:

    标签: javascript html angular angular-reactive-forms angular-validation


    【解决方案1】:

    使用 angular 14 我能够产生与您所描述的结果相似的结果,我不确定它是否会 100% 解决您的问题,但它可能会有所帮助。

    基本上我所做的是创建一个将在组级别应用的验证器函数。该验证器将检查任何给定控件的valid 状态,无论它们是FormGroup 还是FormControl。但是,仅此一项并不能解决问题,就好像您有一个表单组 angular 会看到任何无效的下级控件或组也会使父级无效。所以我所做的是在验证器函数正在检查的任何控件上调用.disable()。这将禁用 UI 元素并通过角度禁用验证检查,从而允许在其中一个子项有效但另一个子项无效时认为父项有效,从而有效地创建 one and only one 验证器。

    我的具体示例是我试图让OnlyOneValidatorMatStepper 工作。

    验证器

    export function onlyOneValidator(controlKeys: string[]) {
        return (control: AbstractControl): ValidationErrors | null => {
            let countOfValidControls = 0;
            for (let key of controlKeys) {
                const controlToCheck = control.get(key);
    
                if (controlToCheck === null || controlToCheck === undefined) {
                    throw new Error(`Error: Invalid control key specified key was ${key}`);
                }
    
                countOfValidControls += controlToCheck?.valid ? 1 : 0;
            }
    
            if (countOfValidControls !== 1) {
                // the count is not exactly one
                return {
                    onlyOneValid: {
                        actualValidCount: countOfValidControls,
                        expectedValidCount: 1
                    }
                };
            }
    
            return null;
        };
    }
    

    控制器

    @Component({
        selector: "app-equipment-creation-page",
        templateUrl: "./equipment-creation-page.component.html",
        styleUrls: ["./equipment-creation-page.component.scss"],
    })
    export class EquipmentCreationPageComponent implements OnInit, OnDestroy {
        public categories = [null, "Tools", "Vehicles"];
    
        constructor(private _formBuilder: FormBuilder) {}
    
    
        public categoryInformationGroup = this._formBuilder.group({
            existingCategory: this._formBuilder.group({
                category: new FormControl(null, [ Validators.required ])
            }),
            newCategory: this._formBuilder.group({
                name: new FormControl("", [Validators.required]),
                description: new FormControl("", [Validators.required])
            })
        }, {
            validators: [
                onlyOneValidator(["existingCategory", "newCategory"])
            ],
        });
    
        public ngOnDestroy(): void {
            this.subscriptions.forEach(sub => {
                sub.unsubscribe();
            });
        }
    
        private subscriptions: Subscription[] = [];
        public ngOnInit(): void {
            this.subscriptions.push(this.categoryInformationGroup.controls.existingCategory.statusChanges.pipe(
                tap((status: string) => {
                    if (status === "VALID") {
                        this.categoryInformationGroup.controls.newCategory.disable();
                    } else {
                        this.categoryInformationGroup.controls.newCategory.enable();
                    }
                })
            ).subscribe());
    
            this.subscriptions.push(this.categoryInformationGroup.controls.newCategory.statusChanges.pipe(
                tap((status: string) => {
                    if (status === "VALID") {
                        this.categoryInformationGroup.controls.existingCategory.disable();
                    } else {
                        this.categoryInformationGroup.controls.existingCategory.enable();
                    }
                })
            ).subscribe());
        }
    }
    
    

    模板

    <form [formGroup]="categoryInformationGroup.controls.existingCategory">
      <mat-form-field>
        <mat-label>Apply to existing category?</mat-label>
        <mat-select formControlName="category">
          <mat-option *ngFor="let category of categories" [value]="category">
            {{ category ?? "None" }}
          </mat-option>
        </mat-select>
      </mat-form-field>
    </form>
    
    OR
    
    <form [formGroup]="categoryInformationGroup.controls.newCategory">
      <mat-form-field>
        <mat-label>Create New Category</mat-label>
        <input matInput formControlName="name" placeholder="Name">
        <mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.name.hasError('required')">This field
          is required
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Create New Category</mat-label>
        <input matInput formControlName="description" placeholder="Description">
        <mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.description.hasError('required')">
          This field is required
        </mat-error>
      </mat-form-field>
    </form>
    

    希望这对您有所帮助或至少为您提供一些有关如何解决此问题的想法。如果其他人对此有任何想法,请告诉我,我很想找到更好的方法来做到这一点。

    【讨论】:

      猜你喜欢
      • 2018-04-16
      • 2021-10-25
      • 1970-01-01
      • 2022-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-28
      • 1970-01-01
      相关资源
      最近更新 更多