带有嵌套表单数组的 Angular 反应表单 [英] Angular Reactive Forms with nested Form Arrays

查看:28
本文介绍了带有嵌套表单数组的 Angular 反应表单的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是 Angular 2 的新手,我认为最好的学习方式是阅读官方的 Angular 指南.

我阅读了反应式表单指南https://angular.io/guide/reactive-forms

演示链接:https://stackblitz.com/angular/jammvmbrpxle

虽然内容总体上还不错,但我一直在思考如何实现更复杂的表单.在给定的示例中,每个 Hero 都有可能拥有多个地址.地址本身是一个平面对象.

如果地址有附加信息,例如位于该地址的房间的颜色和类型会怎样.

导出类地址{街道 = '';城市 = '';状态 = '';zip = '';房间 = 房间 [];}出口班室{类型 = '';}

使表单模型看起来像这样...

createForm() {this.heroForm = this.fb.group({姓名: '',秘密基地:this.fb.array([这个.fb.group({街道: '',城市: '',状态: '',压缩: '',房间:this.fb.array([这个.fb.group({类型: ''})]),})]),力量: '',搭档:''});

}

编辑 - 与 ngOnChanges 一起使用的最终代码

hero-detail.component.ts

createForm() {this.heroForm = this.fb.group({姓名: '',秘密基地:this.fb.array([这个.fb.group({街道: '',城市: '',状态: '',压缩: '',房间:this.fb.array([这个.fb.group({类型: ''})])})]),力量: '',搭档:''});}ngOnChanges() {this.heroForm.reset({名称:this.hero.name,});this.setAddresses(this.hero.addresses);}设置地址(地址:地址[]){让控制 = this.fb.array([]);address.forEach(x => {control.push(this.fb.group({街道:x.street,城市:x.city,状态:x.state,邮编:x.zip,房间:this.setRooms(x) }))})this.heroForm.setControl('secretLairs', control);}设置房间(x){让 arr = new FormArray([])x.rooms.forEach(y => {arr.push(this.fb.group({类型:y.type}))})返回 arr;}

hero-detail.component.html(嵌套表单数组部分)

<div *ngFor="let address of heroForm.get('secretLairs').controls; let i=index" [formGroupName]="i" ><!-- 重复地址模板--><h4>地址#{{i + 1}}</h4><div style="margin-left: 1em;"><div class="form-group"><label class="center-block">街道:<input class="form-control" formControlName="street">

<div class="form-group"><label class="center-block">城市:<input class="form-control" formControlName="city">

<div class="form-group"><label class="center-block">状态:<select class="form-control" formControlName="state"><option *ngFor="let state of states" [value]="state">{{state}}</option></选择>

<div class="form-group"><label class="center-block">邮政编码:<input class="form-control" formControlName="zip">

<br><!-- 重复地址模板结束--><div formArrayName="rooms" class="well well-lg"><div *ngFor="let room of address.get('rooms').controls; let j=index" [formGroupName]="j" ><h4>房间#{{j + 1}}</h4><div class="form-group"><label class="center-block">类型:<input class="form-control" formControlName="type">

<button (click)="addLair()" type="button">添加秘密巢穴</button>

解决方案

2021 随着类型检查变得更加严格(很好!)我们需要做一些改变.使用 getter 无法键入嵌套的 formarray.您可以改用函数,但我不喜欢这个想法,因为每次更改检测都会调用它.相反,我正在解决类型检查并使用 ['controls'] 代替.如果您确实希望对嵌套数组(项目)进行更强的类型化,请使用函数,但请记住,每次更改检测时都会调用它...所以这是更新后的代码:

嵌套表单数组并没有太大不同.基本上你只是复制你的代码......使用嵌套数组:)所以这里有一个示例:

myForm: FormGroup;构造函数(私人FB:FormBuilder){this.myForm = this.fb.group({//如果你愿意,你也可以在里面设置初始表单组公司:this.fb.array([])})}//getter 更容易访问获取公司FormArr():FormArray {将 this.myForm.get('companys') 作为 FormArray 返回;}添加新公司(){this.companysFormArr.push(这个.fb.group({公司: [''],项目:this.fb.array([])}));}删除公司(索引:数字){this.companiesFormArr.removeAt(index);}

所以这是最外层表单数组的添加和删除,因此向嵌套表单数组添加和删除表单组只是重复代码.从模板中,我们将当前表单组传递到要添加(在本例中)新项目/删除项目的数组.

addNewProject(control) {控制推(这个.fb.group({项目名: ['']}))}删除项目(控制,索引){control.removeAt(index)}

和模板一样,你迭代你的外部表单数组,然后在里面迭代你的内部表单数组:

<div formArrayName=公司"><div *ngFor="let comp of CompaniesFormArr.controls;让 i=index"><h3>公司{{i+1}}:</h3><div [formGroupName]=i"><input formControlName="公司";/><按钮(点击)=删除公司(i)">删除公司<div formArrayName="projects"><!-- 在这里我已经解决了类型检查,如果你想要更强的类型检查,调用一个函数.请记住:在每次更改检测时调用的函数!--><div *ngFor="let project of comp.get('projects')['controls'];让 j=index"><h4>项目{{j+1}}</h4><div [formGroupName]=j"><input formControlName="projectName";/><按钮(点击)=deleteProject(comp.get('projects'), j)">删除项目

<按钮(点击)=addNewProject(comp.get('projects'))">添加新项目

演示

要在获得数据后为您的表单设置值,您可以调用以下方法来迭代您的数据并将值设置为您的表单.在这种情况下,data 看起来像:

data = {公司: [{公司:示例公司",项目:[{项目名称:示例项目",}]}]}

我们调用 setCompanies 来为我们的表单设置值:

setCompanies() {this.data.companies.forEach(x => {this.companiesFormArr.push(this.fb.group({公司:x.company,项目:this.setProjects(x) }))})}设置项目(x){让 arr = new FormArray([])x.projects.forEach(y => {arr.push(this.fb.group({项目名称:y.projectName}))})返回 arr;}

I'm new to Angular 2 and decided the best way to learn would be to go through the official Angular guides.

I went through the Reactive Forms Guide https://angular.io/guide/reactive-forms

demo link: https://stackblitz.com/angular/jammvmbrpxle

While the content was overall pretty good, I'm stuck on how I would go about implementing a more complex Form. In the given example, each Hero has the potential for many addresses. An address itself is a flat object.

What if Addresses had additional information such as the color and type of rooms located at the address.

export class Address {
    street = '';
    city   = '';
    state  = '';
    zip    = '';
    rooms = Room[];
}

export class Room {
     type = '';
}

so that the form model would look like this...

createForm() {
this.heroForm = this.fb.group({
  name: '',
  secretLairs: this.fb.array([
      this.fb.group({
          street: '',
          city: '',
          state: '',
          zip: '',
          rooms: this.fb.array([
              this.fb.group({
                 type: ''
          })]),
      })]),
  power: '',
  sidekick: ''
});

}

EDIT - Finalized Code that works with ngOnChanges

hero-detail.component.ts

createForm() {
    this.heroForm = this.fb.group({
      name: '',
      secretLairs: this.fb.array([
        this.fb.group({
          street: '',
          city: '',
          state: '',
          zip: '',
          rooms: this.fb.array([
            this.fb.group({
              type: ''
            })
          ])
        })
      ]),
      power: '',
      sidekick: ''
    });
  }

  ngOnChanges() {
    this.heroForm.reset({
      name: this.hero.name,
    });
    this.setAddresses(this.hero.addresses);
  }

  setAddresses(addresses: Address[]) {
    let control = this.fb.array([]);
    addresses.forEach(x => {
      control.push(this.fb.group({
        street: x.street,
        city: x.city,
        state: x.state,
        zip: x.zip,
        rooms: this.setRooms(x) }))
    })
    this.heroForm.setControl('secretLairs', control);
  }

  setRooms(x) {
    let arr = new FormArray([])
    x.rooms.forEach(y => {
      arr.push(this.fb.group({ 
        type: y.type 
      }))
    })
    return arr;
  }

hero-detail.component.html (the nested form array portion)

<div formArrayName="secretLairs" class="well well-lg">
  <div *ngFor="let address of heroForm.get('secretLairs').controls; let i=index" [formGroupName]="i" >
    <!-- The repeated address template -->
    <h4>Address #{{i + 1}}</h4>
    <div style="margin-left: 1em;">
      <div class="form-group">
        <label class="center-block">Street:
          <input class="form-control" formControlName="street">
        </label>
      </div>
      <div class="form-group">
        <label class="center-block">City:
          <input class="form-control" formControlName="city">
        </label>
      </div>
      <div class="form-group">
        <label class="center-block">State:
          <select class="form-control" formControlName="state">
            <option *ngFor="let state of states" [value]="state">{{state}}</option>
          </select>
        </label>
      </div>
      <div class="form-group">
        <label class="center-block">Zip Code:
          <input class="form-control" formControlName="zip">
        </label>
      </div>
    </div>
    <br>
    <!-- End of the repeated address template -->
    <div formArrayName="rooms" class="well well-lg">
      <div *ngFor="let room of address.get('rooms').controls; let j=index" [formGroupName]="j" >
          <h4>Room #{{j + 1}}</h4>
          <div class="form-group">
            <label class="center-block">Type:
              <input class="form-control" formControlName="type">
            </label>
          </div>
      </div>
    </div>
  </div>
  <button (click)="addLair()" type="button">Add a Secret Lair</button>
</div>

解决方案

EDIT: 2021 As the typechecking has become more strict (good!) we need to do some changes. Typing the nested formarray cannot be done using a getter. You can use a function instead, but I don't like that idea, as it is called on each change detection. Instead I am working around the typechecking and using ['controls'] instead. If you do want stronger typing for nested array (projects) use a function, but remember the fact that it is called on each change detection... So here is the updated code:

It's not very much different to have a nested formarray. Basically you just duplicate the code you have... with nested array :) So here's a sample:

myForm: FormGroup;

constructor(private fb: FormBuilder) {
  this.myForm = this.fb.group({
    // you can also set initial formgroup inside if you like
    companies: this.fb.array([])
  })
}

// getter for easier access
get companiesFormArr(): FormArray {
  return this.myForm.get('companies') as FormArray;
}

addNewCompany() {
  this.companiesFormArr.push(
    this.fb.group({
      company: [''],
      projects: this.fb.array([])
    })
  );
}

deleteCompany(index: number) {
  this.companiesFormArr.removeAt(index);
}

So that is the add and delete for the outermost form array, so adding and removing formgroups to the nested form array is just duplicating the code. Where from the template we pass the current formgroup to which array you want to add (in this case) a new project/delete a project.

addNewProject(control) {
  control.push(
    this.fb.group({
      projectName: ['']
  }))
}

deleteProject(control, index) {
  control.removeAt(index)
}

And the template in the same manner, you iterate your outer formarray, and then inside that iterate your inner form array:

<form [formGroup]="myForm">
  <div formArrayName="companies">
    <div *ngFor="let comp of companiesFormArr.controls; let i=index">
    <h3>COMPANY {{i+1}}: </h3>
    <div [formGroupName]="i">
      <input formControlName="company" />
      <button (click)="deleteCompany(i)">
         Delete Company
      </button>
      <div formArrayName="projects">
        <!-- Here I has worked around the typechecking, 
             if you want stronger typechecking, call a function. 
             Remember: function called on each change detection! -->
        <div *ngFor="let project of comp.get('projects')['controls']; let j=index">
          <h4>PROJECT {{j+1}}</h4>
          <div [formGroupName]="j">
            <input formControlName="projectName" />
            <button (click)="deleteProject(comp.get('projects'), j)">
              Delete Project
            </button>
          </div>
        </div>
        <button (click)="addNewProject(comp.get('projects'))">
          Add new Project
        </button>
      </div>
    </div>
  </div>
</div>

DEMO

EDIT:

To set values to your form once you have data, you can call the following methods that will iterate your data and set the values to your form. In this case data looks like:

data = {
  companies: [
    {
      company: "example comany",
      projects: [
        {
          projectName: "example project",
        }
      ]
    }
  ]
}

We call setCompanies to set values to our form:

setCompanies() {
  this.data.companies.forEach(x => {
    this.companiesFormArr.push(this.fb.group({ 
      company: x.company, 
      projects: this.setProjects(x) }))
  })
}

setProjects(x) {
  let arr = new FormArray([])
  x.projects.forEach(y => {
    arr.push(this.fb.group({ 
      projectName: y.projectName 
    }))
  })
  return arr;
}

这篇关于带有嵌套表单数组的 Angular 反应表单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆