Angular 5 – 动态 FormArray 和简单验证的响应式表单





5.00/5 (4投票s)
如何使用 Angular 5 响应式表单动态显示 FormArray 中的控件,并根据选择启用/禁用验证器
目录
引言
最近,我正在使用 Angular 5 响应式表单创建一个注册表单。表单的一部分要求动态生成复选框元素。因此,表单上可能有 3、4、5 个或更多的复选框。如果选中复选框,旁边会显示一个新的FormControl
/HTML 元素。这可能是一个textbox
、dropdown
或单选按钮列表类型,具体取决于选中的项目,并且它们是必填字段。在本文中,我将分享如何完成以下任务。如果您有不同意见,请分享,我将非常感激。
- 动态显示
FormArray
中的复选框 - 根据复选框选择隐藏和显示表单控件
- 根据复选框选择添加和删除必填字段验证器
- 为单选按钮列表添加必填字段指示器
- 显示必填字段指示器
- 收集选中的复选框和动态控件值
显示必填字段指示器
与其在每个表单元素旁边显示必填字段消息,不如通过使用级联样式表 (CSS) 选择器来编程表单以显示错误指示器。例如,我们可以使用::after
选择器在标签旁边附加一个星号,字体颜色为红色。
.required::after {
content: " *";
color: red;
}
或者,如果控件具有ng-invalid
类,则使表单控件边框变为红色。图 1 显示了使用清单 1 和清单 2 中的 CSS 产生的输出结果。
.form-control.ng-invalid {
border-left:5px solid red;
}
单选按钮列表有点棘手,基于示例应用程序,我们可以利用清单 3 中的 CSS 组合器和伪类在标签旁边附加一个星号。基本原理是,选择器将选择无效状态下表单控件下的所有标签,并在其旁边附加一个星号,字体颜色为红色。图 2 显示了使用清单 3 中的 CSS 生成的表单控件输出。
.form-control.ng-invalid ~ label.checkbox-inline::after {
content: " *";
color: red;
}
动态显示 FormArray 中的复选框
清单 4 展示了如何利用FormBuilder.group
工厂方法创建一个包含firstName
、lastName
、email
和programmingLanguage FormControl
的FormGroup
。前三个FormControl
是必填的,并且电子邮件必须匹配正则表达式要求。programmingLanguage
值的类型是FormArray
,它将使用Checkbox
和其他输入类型来托管可用编程语言的数组。
this.sampleForm = this.formBuilder.group({
firstName: new FormControl('', Validators.required),
lastName: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required,Validators.pattern(this.regEmail)]),
programmingLanguage: this.formBuilder.array([{}])
});
清单 5 展示了应用程序将使用的示例对象和数据。
class Item {
constructor(
private text: string,
private value: number) { }
}
class FormControlMetadata {
constructor(
private checkboxName: string,
private checkboxLabel: string,
private associateControlName: string,
private associateControlLabel: string,
private associateControlType: string,
private associateControlData: Array<Item>) { }
}
this.programmingLanguageList = [
new Item('PHP',1),
new Item('JavaScript',2),
new Item('C#',3),
new Item('Other',4)];
this.otherProgrammingLanguageList = [
new Item('Python',1),
new Item('Ruby',2),
new Item('C++',3),
new Item('Rust',4)];
this.phpVersionList = [
new Item('v4',1),
new Item('v5',2),
new Item('v6',3),
new Item('v7',4)];
下一步是填充programmingLanguage FormArray
。此FormArray
将包含一个FormGroup
数组,每个FormGroup
将托管多个FormControl
实例。清单 6 展示了用于填充FormArray
的逻辑以及稍后将由 HTML 模板使用的langControlMetadata
对象。后者对象用于存储元素/控件的属性,如Checkbox
、Textbox
、单选按钮、下拉列表等……
最初,代码将遍历programmingLanguageList
对象的属性并填充langControlMetadata
对象。Checkbox
和相关的 HTML 元素名称将是静态文本和数据源中的Item.value
/key
的组合。这些属性将在 HTML 模板中映射到formControlName
。Checkbox
标签将来自Item.text
。associateControlLabel
属性将用作input
元素的占位符属性。默认情况下,associateControlControlType
将是textbox
,在此示例中,如果选中 PHP 选项,应用程序将显示单选按钮列表,如果选中 Other 选项,则显示下拉列表。associateControlData
属性的目的是保存单选按钮和下拉列表元素的 数据源。
下一步是创建两个FormControl
,一个用于Checkbox
元素,一个用于associate
元素。默认情况下,associate
元素是禁用的。然后,将先前创建的子控件插入FormGroup
中。键将与Checkbox
和associate
元素名称相同。最后,将FormGroup
实例插入programmingLanguage
对象中。
enum ControlType {
textbox =1 ,
dropdown = 2,
radioButtonList = 3
}
export class Common {
public static ControlType = ControlType;
public static CheckboxPrefix = 'cbLanguage_';
public static OtherPrefix ='otherValue_';
}
langControlMetada: Array<FormControlMetadata> = [];
populateProgrammingLanguage() {
//get the property
this.programmingFormArray = this.sampleForm.get('programmingLanguage') as FormArray;
//clear
this.programmingFormArray.removeAt(0);
let p:Item;
//loop through the list and create the formarray metadata
for (p of this.programmingLanguageList) {
let control = new FormControlMetadata();
let group = this.formBuilder.group({});
//create the checkbox and other form element metadata
control.checkboxName = `${Common.CheckboxPrefix}${p.value}`;
control.checkboxLabel = p.text;
control.associateControlName = `${Common.OtherPrefix}${p.value}`;
control.associateControlLabel = `${p.text} comments`;
control.associateControlType = Common.ControlType[Common.ControlType.textbox];
//assume 1 is radio button list
if (p.value == 1) {
control.associateControlType = Common.ControlType[Common.ControlType.radioButtonList];
control.associateControlData = this.phpVersionList;
}
//just assumed id 4 is dropdown
if (p.value == 4) {
control.associateControlType = Common.ControlType[Common.ControlType.dropdown];
control.associateControlData = this.otherProgrammingLanguageList;
}
//store in array, use by html to loop through
this.langControlMetada.push(control);
//form control
let checkBoxControl = this.formBuilder.control('');
let associateControl = this.formBuilder.control({ value: '', disabled: true });
//add to form group [key, control]
group.addControl(`${Common.CheckboxPrefix}${p.value}`, checkBoxControl);
group.addControl(`${Common.OtherPrefix}${p.value}`, associateControl);
//add to form array
this.programmingFormArray.push(group);
}
}
清单 7 展示了programmingLanguage FormArray
如何在 HTML 模板中渲染。它使用NgFor
指令根据langControlMetadata
对象中的属性构建“您最喜欢的编程语言”部分。模板非常直接,第一个元素是Checkbox
,它关联了一个更改事件。第二个元素将根据item.associateControlType
的值渲染为textbox
、单选按钮或下拉列表。
<div class="form-group row" formArrayName="programmingLanguage">
<div class="col-xs-12"
*ngFor="let item of langControlMetada; let i = index;">
<div [formGroupName]="i">
<div class="form-group row">
<div class="form-inline" style="margin-left:15px;">
<div class="form-check">
<label [for]="item.checkboxName" class="form-check-label">
<input type="checkbox" class="form-check-input"
[id]="item.checkboxName"
(change)="languageSelectionChange
(i, item.checkboxName, item.associateControlName)"
[formControlName]="item.checkboxName"> {{ item.checkboxLabel}}
</label>
<input *ngIf="item.associateControlType == 'textbox'"
class="form-control form-control-sm"
id="item.associateControlName"
[placeholder]="item.associateControlLabel" maxlength="255"
[formControlName]="item.associateControlName" />
<span *ngIf="item.associateControlType == 'dropdown'">
<select class="form-control form-control-sm"
[formControlName]="item.associateControlName">
<option *ngFor="let item of item.associateControlData"
[value]="item.value">
{{item.text}}
</option>
</select>
</span>
<span *ngIf="item.associateControlType == 'radioButtonList'">
<span *ngFor="let option of item.associateControlData">
<input #version type="radio" [formControlName]=
"item.associateControlName"
class="form-control form-control-sm"
[value]="option.value">
<label class="checkbox-inline" *ngIf="!version.disabled">
{{option.text}}</label>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
根据复选框选择隐藏和显示表单控件
清单 8 展示了通过复选框选中更改事件启用和禁用、添加和删除元素验证器的函数。如果选中复选框,业务逻辑将使用enable()
方法启用associate
控件/元素并将字段设置为必填。updateValueAndValidity()
方法用于更新控件的值和验证状态。另一方面,取消选中复选框将清除associate
元素的值,禁用它并删除验证器。
languageSelectionChange(pos: number, cnkName: string, txtName: string) {
let programmingLanguage = this.programmingLanguages();
let control = programmingLanguage.controls[pos] as FormGroup
if (control.controls[cnkName].value == true) {
//checkbox checked
control.controls[txtName].enable();
control.controls[txtName].setValidators([Validators.required]);
control.controls[txtName].updateValueAndValidity();
this.selectedLanguageCount++;
}
else {
//unchecked
control.controls[txtName].setValue('');
control.controls[txtName].disable();
control.controls[txtName].clearValidators();
control.controls[txtName].updateValueAndValidity();
this.selectedLanguageCount--;
}
}
清单 9 显示了隐藏具有disabled
属性的控件的 CSS。
.form-control:disabled {
display: none;
}
收集选中的复选框和动态控件值
一旦表单有效,提交按钮将变得可点击。我们将保持简单,将选中的复选框 ID 和associate
控件/元素值发送到 API。
清单 10 显示了按钮点击事件的代码。代码将遍历programmingLanguage
控件(即FormArray
),以获取FormGroup
中选中的checkbox
和associate
控件值。之前,checkbox
和associate
控件名称是通过结合静态文本和数据源的值/键来生成的。为了获取checkbox
ID,首先,从FormGroup
获取第一个控件名称。然后,删除静态文本并将其解析为数字。之后,使用 ID 生成复选框和associate
控件名称,并使用该名称通过名称访问FormGroup
中的控件值。
例如,如果 ID 是3
,则checkbox
和associate
控件名称将分别是“cbLanguage_3
”和“otherValue_3
”。然后使用名称查询FormGroup
以检查checkbox
值是否为true
,如果是,则收集id
和associate
控件值。有关更多详细信息,请参阅清单 10。
programmingLanguages(): FormArray {
return this.sampleForm.get('programmingLanguage') as FormArray;
};
public submit(e: any): void {
e.preventDefault();
//reset
let selectedLanguageList: Array<Item> = [];
let programmingLanguage = this.programmingLanguages();
let i: number;
//checkbox id
let languageId: number = 0;
for(i = 0; i < programmingLanguage.controls.length; i++) {
let control = programmingLanguage.controls[i] as FormGroup
let selectedLanguage: Language = {} as any;
//get the selected checkbox id
for (var k in control.controls) {
languageId = Number(k.replace(/[a-zA-Z_]/g, ""));
break;
}
//capture the selected checkbox Id and textbox value
if (control.controls[`${Common.CheckboxPrefix}${languageId}`].value == true) {
selectedLanguage.value = languageId;
selectedLanguage.text = control.controls[`${Common.OtherPrefix}${languageId}`].value
selectedLanguageList.push(selectedLanguage);
}
}
if (selectedLanguageList.length == 0) {
this.missingLanguage = true;
} else {
//submit to API
let formObjectToApi = new FormControlMetadata();
formObjectToApi.lastName = this.sampleForm.controls['lastName'].value;
formObjectToApi.firstName = this.sampleForm.controls['firstName'].value;
formObjectToApi.email = this.sampleForm.controls['email'].value;
formObjectToApi.selectedLanguages = selectedLanguageList;
this.missingLanguage = false;
this.test = formObjectToApi;
}
}
结论
我希望有人能发现这些信息有用,并能让您的编程工作更轻松。如果您发现任何错误或不同意内容,或者想帮助改进本文,请给我留言,我将与您合作进行更正。我建议您访问演示站点进行探索,以便全面掌握概念,因为我可能在本文中遗漏了一些重要信息。如果您想帮助改进本文,请联系我。
历史
- 2018 年 4 月 15 日 - 初始版本
- 2018 年 4 月 28 日 - 更新了演示站点的链接