【问题标题】:FormControl validators not working sometimes, but working where they shouldn't othersFormControl 验证器有时不工作,但在其他人不应该工作的地方工作
【发布时间】:2021-01-27 20:09:57
【问题描述】:

我正在 Angular 中创建一个反应式表单,其中涉及用户对任意数量的部件提出请求,这些部件显示为输入行。

对于每个部分,某些字段是必需的,而某些字段只能是数字。我为此添加了验证器,但无论发生什么,输入都不会显示为无效。我有验证器在工作正常的部分之外,当输入为空时显示错误。

在另一端,我有一个可选的“备注”字段,没有验证器,它的行为就像它有 Validators.required 并且在为空时无效。

是我做错了什么导致了这些问题吗?我已经尝试寻找解决方案,但到目前为止没有运气。

This is the program running, showing an empty "Notes" input being invalid and a "Quantity" input with letters not being invalid.

create.component.html

<h1>New Request</h1>

<div>
  <form [formGroup]="reqForm" (ngSubmit)="onSubmit(reqForm.value)">
    <div class="form-row">
      <div class="form-group col-md-6">
        <label for="name">Name</label>
        <input type="text" name="name" class="form-control" id="name" placeholder="Name" formControlName="name"/>
        <div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert alert-danger">
          <div *ngIf="name.errors.required">
            Name is required
          </div>
        </div>
      </div>
      <div class="form-group col-md-6">
        <label for="reqNo">Req No.</label>
        <input type="text" class="form-control" id="reqNo" name="reqNo"
               placeholder="Numbers only, will auto-assign if blank" formControlName="reqNo"/>
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-md-9">
        <label for="source">Recommended Source</label>
        <input type="text" name="source" class="form-control" id="source" placeholder="Source" formControlName="source"/>
        <div *ngIf="source.invalid && (source.dirty || source.touched)" class="alert alert-danger">
          <div *ngIf="source.errors.required">
            Source is required
          </div>
        </div>
      </div>
      <div class="form-group col-md-3">
        <label for="dateRequired">Date Required</label>
        <input name="dateRequired" class="form-control dateRequired"
               placeholder="Date Required" matInput [matDatepicker]="dateRequired" formControlName="date">
        <div class="dateRequiredPicker">
          <mat-datepicker-toggle matSuffix [for]="dateRequired"></mat-datepicker-toggle>
          <mat-datepicker #dateRequired></mat-datepicker>
        </div>
        <div *ngIf="date.invalid && (date.dirty || date.touched)" class="alert alert-danger">
          Invalid date <br />(Ex. 1/1/2020)
        </div>
      </div>
    </div>
    <h2>Parts</h2>
    <div class="form-row">
      <div class="col-md-1">
        <h5>Quantity</h5>
      </div>
      <div class="col-md-1">
        <h5>Unit</h5>
      </div>
      <div class="col-md-2">
        <h5>Part Number</h5>
      </div>
      <div class="col-md-4">
        <h5>Description</h5>
      </div>
      <div class="col-md-2">
        <h5>Unit Cost</h5>
      </div>
      <div class="col-md-2">
        <h5>Total Cost</h5>
      </div>
    </div>
    <div formArrayName="parts">
      <div *ngFor="let part of reqForm.controls.parts.controls; let i = index" name="parts[i]" [formGroupName]="i" ngDefaultControl>
        <div class="form-row partRow" id="partRow{{i}}">
          <div class="col-md-1">
            <input type="text" class="form-control quantity" name="parts[{{i}}].quantity"
                   id="quantity{{i}}" placeholder="Quantity" formControlName="quantity"/>
          </div>
          <div class="col-md-1">
            <select name="parts[{{i}}].unit" id="unit{{i}}" class="form-control unit" formControlName="unit">
              <option *ngFor="let unitOption of unitOptions" [value]="unitOption">{{unitOption}}</option>
            </select>
          </div>
          <div class="col-md-2">
            <input type="text" class="form-control supplierPN" name="parts[{{i}}].supplierPN"
                   id="supplierPN{{i}}" placeholder="Part Number" formControlName="supplierPN"/>
          </div>
          <div class="col-md-4">
            <input type="text" class="form-control description" name="parts[{{i}}].description"
                   id="description{{i}}" placeholder="Description" formControlName="description"/>
          </div>
          <div class="col-md-2">
            <input type="text" class="form-control unitCost" name="parts[{{i}}].unitCost"
                   id="unitCost{{i}}" placeholder="Unit Cost" formControlName="unitCost"/>
          </div>
          <div class="col-md-2 totalCost">
            <span *ngIf="isNumber(reqForm.value.parts[i].quantity) && isNumber(reqForm.value.parts[i].unitCost); else noTotalCost">
              {{reqForm.value.parts[i].quantity * reqForm.value.parts[i].unitCost | currency : "$"}}
            </span>
            <ng-template #noTotalCost>N/A</ng-template>
          </div>
        </div>
      </div>
    </div>
    <div class="form-row buttons">
      <button type="button" mat-mini-fab color="primary" id="addRow" (click)="addRow()">+</button>
      <button type="button" mat-mini-fab color="warn" id="delRow" (click)="delRow()">-</button>
      <button type="button" mat-raised-button color="primary" (click)="copyPasteDialog()" id="pasteParts">Copy/Paste Parts From Excel</button>
    </div>
    <hr />
    <div class="form-row">
      <div class="col-md-2 form-check">
        <input type="checkbox" id="taxExempt" class="form-check-input" name="taxExempt" formControlName="taxExempt"/>
        <label class="form-check-label" for="taxExempt">Tax Exempt</label>
      </div>
      <div class="col-md-6"></div>
      <div class="col-md-2">
        <h5>Overall Cost:</h5>
      </div>
      <div class="col-md-2 overallCost">
        <span *ngIf="checkOverallCost(); else noOverallCost">{{overallCost | currency : "$"}}</span>
        <ng-template #noOverallCost>N/A</ng-template>
      </div>
    </div>
    <div class="form-row">
      <label for="notes">Notes</label>
      <input required type="text" class="form-control" id="notes" placeholder="Notes" name="notes" formControlName="notes"/>
    </div>
    <div class="form-row form-buttons">
      <button mat-raised-button color="warn" type="reset">Clear</button>
      <button mat-raised-button color="primary" [disabled]="reqForm.invalid" id="submit" type="submit">Submit</button>
    </div>
  </form>
</div>

create.component.ts

import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { CreateService } from '../create.service';
import { PurReqPart } from '../interface';
import { CopyPasteDialogComponent } from '../copy-paste-dialog/copy-paste-dialog.component';
import { FormBuilder, FormArray, Validators } from '@angular/forms';

@Component({
  selector: 'app-create',
  templateUrl: './create.component.html',
  styleUrls: ['./create.component.css']
})
export class CreateComponent implements OnInit {
  unitOptions = ["Case", "Carton", "Dozen", "Each", "/Hour"]; // Used in an *ngFor for Unit select
  count: number = 0;
  overallCost;

  reqForm;
  get parts(): FormArray {
    return this.reqForm.get('parts') as FormArray;
  }

  constructor(private createService: CreateService, public dialog: MatDialog, private formBuilder: FormBuilder) {
    this.reqForm = this.formBuilder.group({
      name: ["", [
        Validators.required,
      ]],
      reqNo: [""],
      source: ["", [
        Validators.required
      ]],
      date: [new Date(), [
        Validators.required
      ]],
      taxExempt: [false],
      notes: [""],
      parts: this.formBuilder.array([
        this.formBuilder.group(new PurReqPart()),
      ]),
    });
  }
  
  ... // Other code is unrelated, validators are not set outside of initializing form controls

}

interface.ts (PurReqPart)

import { Validators } from "@angular/forms";

export class PurReqPart {
  numbersOnlyRegEx = '^[0-9]*$';
  constructor(
    public quantity?,
    public unit?,
    public supplierPN?,
    public description?,
    public unitCost?
  ) {
    quantity: [quantity, [
      Validators.required,
      Validators.pattern(this.numbersOnlyRegEx)
    ]];
    unit: [unit, [
      Validators.required
    ]];
    supplierPN: [supplierPN, []];
    description: [description, []];
    unitCost: [unitCost, [
      Validators.required
    ]];
  }
}

【问题讨论】:

  • 我想将interface.ts 中的:s 更改为=s 但这并没有改变任何东西
  • 欢迎来到恐怖的表单和表单控件!我在 Form Controls 上做了一个 7 个月的系列,得出的结论是使用 NgModel 要好得多。这是我写的文章:dev.to/jwp/angular-ngmodel-model-and-viewmodel-5m
  • @JohnPeters,不是,真的不是,也许没有人会正确地向您解释反应形式。 (真的有两种方法可以创建 FormGroup - 使用 FormBuilder 或直接不帮助 -)但只有验证器才能使反应式表单非常值得。订阅 valueChange 的可能性 - 使用 Observables 的强大功能 - 或者在提交或模糊时不检查 else 是另一个有趣的能力。当然你需要一个新的解决方法来响应式表单
  • 其实你已经离题了,我花了 7 个月的时间调查反应式表单的谬误,并得出结论,如果 ngModel 更胜一筹。在那篇文章中,我展示了如何使模型和视图之间的更改保持同步。只需三行 html 代码即可完成。与上面显示的相比,这是多么容易。验证仍然可以完成,在 html 后面的代码中没有。
  • @JohnPeters,制作一个每次更改都会调用 api 的控件(但您需要 debounceTime),或者验证一个不等于另一个控件,或者进行 asyncValidation 并指示该控件正在等待验证...我无法想象没有反应式表单如何制作东西。顺便说一下,检查netbasal.com/… 和 Ward Bell 的评论 - 更接近你的意见 - 或者这个 SO stackoverflow.com/questions/39142616/…

标签: angular typescript angular-reactive-forms


【解决方案1】:

James,恐怕 formBuilder 方法不会“解构”参数,所以你不能这样做

//this NOT work
const obj=['',[Validators.maxLength(10)]]
this.control=this.formBuilder.control(obj)

所以你不能做你想做的事。你可以做一个函数,有些像

getPurReqPart(data:any=null)
{
   data=data || {quantity:0,unit:''...}
   return formBuilder.group({
      quantity:[data.quantity,[Validators.required,Validators.pattern(this.numbersOnlyRegEx)],
      ...
   })
}

但不在界面中。 Angular 不像 ASP.NET 的 DataAnotations - 我认为你正在尝试一些类似的 -

【讨论】:

  • 谢谢!这解决了部件验证器无法正常工作的问题,我不知道 formBuilder 不会解构它的参数。不幸的是,尽管没有验证器,我仍然对为什么我的“注释”部分仍然表现得像它应该是必需的一样感到困惑。
【解决方案2】:

您的addRow() 功能是什么? 我认为您还需要在此处明确您的验证器。

【讨论】:

  • addRow() 将另一个PurReqPart 对象推入parts 数组,验证器包含在PurReqPart 的构造函数中
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-25
  • 1970-01-01
  • 1970-01-01
  • 2018-09-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多