【发布时间】:2021-01-27 20:09:57
【问题描述】:
我正在 Angular 中创建一个反应式表单,其中涉及用户对任意数量的部件提出请求,这些部件显示为输入行。
对于每个部分,某些字段是必需的,而某些字段只能是数字。我为此添加了验证器,但无论发生什么,输入都不会显示为无效。我有验证器在工作正常的部分之外,当输入为空时显示错误。
在另一端,我有一个可选的“备注”字段,没有验证器,它的行为就像它有 Validators.required 并且在为空时无效。
是我做错了什么导致了这些问题吗?我已经尝试寻找解决方案,但到目前为止没有运气。
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