65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2018 年 4 月 15 日

CPOL

6分钟阅读

viewsIcon

60698

downloadIcon

14

如何使用 Angular 5 响应式表单动态显示 FormArray 中的控件,并根据选择启用/禁用验证器

angular 5 FormArray

目录

引言

最近,我正在使用 Angular 5 响应式表单创建一个注册表单。表单的一部分要求动态生成复选框元素。因此,表单上可能有 3、4、5 个或更多的复选框。如果选中复选框,旁边会显示一个新的FormControl/HTML 元素。这可能是一个textboxdropdown或单选按钮列表类型,具体取决于选中的项目,并且它们是必填字段。在本文中,我将分享如何完成以下任务。如果您有不同意见,请分享,我将非常感激。

  • 动态显示FormArray中的复选框
  • 根据复选框选择隐藏和显示表单控件
  • 根据复选框选择添加和删除必填字段验证器
  • 为单选按钮列表添加必填字段指示器
  • 显示必填字段指示器
  • 收集选中的复选框和动态控件值

显示必填字段指示器

与其在每个表单元素旁边显示必填字段消息,不如通过使用级联样式表 (CSS) 选择器来编程表单以显示错误指示器。例如,我们可以使用::after选择器在标签旁边附加一个星号,字体颜色为红色。

列表 1
.required::after {
    content: " *";
    color: red;
}

或者,如果控件具有ng-invalid类,则使表单控件边框变为红色。图 1 显示了使用清单 1 和清单 2 中的 CSS 产生的输出结果。

列表 2
.form-control.ng-invalid {
    border-left:5px solid red;
}
图 1

Angular 5 FormGroup Required Field

单选按钮列表有点棘手,基于示例应用程序,我们可以利用清单 3 中的 CSS 组合器和伪类在标签旁边附加一个星号。基本原理是,选择器将选择无效状态下表单控件下的所有标签,并在其旁边附加一个星号,字体颜色为红色。图 2 显示了使用清单 3 中的 CSS 生成的表单控件输出。

列表 3
.form-control.ng-invalid ~ label.checkbox-inline::after {
    content: " *";
    color: red;
}
图 2

Angular 5 FormGroup Radio button Required Field

动态显示 FormArray 中的复选框

清单 4 展示了如何利用FormBuilder.group工厂方法创建一个包含firstNamelastNameemailprogrammingLanguage FormControlFormGroup。前三个FormControl是必填的,并且电子邮件必须匹配正则表达式要求。programmingLanguage值的类型是FormArray,它将使用Checkbox和其他输入类型来托管可用编程语言的数组。

列表 4
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 展示了应用程序将使用的示例对象和数据。

列表 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对象。后者对象用于存储元素/控件的属性,如CheckboxTextbox、单选按钮、下拉列表等……

最初,代码将遍历programmingLanguageList对象的属性并填充langControlMetadata对象。Checkbox和相关的 HTML 元素名称将是静态文本和数据源中的Item.value/key的组合。这些属性将在 HTML 模板中映射到formControlNameCheckbox标签将来自Item.textassociateControlLabel属性将用作input元素的占位符属性。默认情况下,associateControlControlType将是textbox,在此示例中,如果选中 PHP 选项,应用程序将显示单选按钮列表,如果选中 Other 选项,则显示下拉列表。associateControlData属性的目的是保存单选按钮和下拉列表元素的 数据源。

下一步是创建两个FormControl,一个用于Checkbox元素,一个用于associate元素。默认情况下,associate元素是禁用的。然后,将先前创建的子控件插入FormGroup中。键将与Checkboxassociate元素名称相同。最后,将FormGroup实例插入programmingLanguage对象中。

列表 6
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、单选按钮或下拉列表。

列表 7
<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元素的值,禁用它并删除验证器。

列表 8
      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。

列表 9
.form-control:disabled {
    display: none;
}

收集选中的复选框和动态控件值

一旦表单有效,提交按钮将变得可点击。我们将保持简单,将选中的复选框 ID 和associate控件/元素值发送到 API。

清单 10 显示了按钮点击事件的代码。代码将遍历programmingLanguage控件(即FormArray),以获取FormGroup中选中的checkboxassociate控件值。之前,checkboxassociate控件名称是通过结合静态文本和数据源的值/键来生成的。为了获取checkbox ID,首先,从FormGroup获取第一个控件名称。然后,删除静态文本并将其解析为数字。之后,使用 ID 生成复选框和associate控件名称,并使用该名称通过名称访问FormGroup中的控件值。

例如,如果 ID 是3,则checkboxassociate控件名称将分别是“cbLanguage_3”和“otherValue_3”。然后使用名称查询FormGroup以检查checkbox值是否为true,如果是,则收集idassociate控件值。有关更多详细信息,请参阅清单 10。

列表 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 日 - 更新了演示站点的链接

观看此脚本演示

资源

© . All rights reserved.