【问题标题】:Angular Reactive Forms with nested Form Arrays带有嵌套表单数组的 Angular 反应式表单
【发布时间】:2018-07-04 07:19:07
【问题描述】:

我是 Angular 2 的新手,并决定最好的学习方式是阅读 Angular 官方指南。

我浏览了响应式表单指南 https://angular.io/guide/reactive-forms

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

虽然内容总体上相当不错,但我仍然坚持如何实现更复杂的表单。在给定的示例中,每个英雄都有可能拥有许多地址。地址本身就是一个平面对象。

如果地址有附加信息,例如地址所在房间的颜色和类型。

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

export class Room {
     type = '';
}

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

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 一起使用的最终代码

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(嵌套表单数组部分)

<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>

【问题讨论】:

    标签: angular formarray


    【解决方案1】:

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

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

    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);
    }
    

    这就是最外层表单数组的添加和删除,所以向嵌套表单数组添加和删除表单组只是重复代码。从模板中,我们将当前表单组传递给您要添加(在这种情况下)新项目/删除项目的数组。

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

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

    <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

    编辑:

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

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

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

    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;
    }
    

    【讨论】:

    • 一如既往的好答案
    • 谢谢 Alex,但这并不能完全解决我的问题。我已经确定了问题是什么,但没有确定如何解决它......假设表单应该预先填充数据......我实际上不确定如何正确设置表单控件。
    • @yurzui 谢谢你,先生,你很善良,同样回馈你! ;)
    • 似乎可以工作,但在我的 IDEA 中出现错误“未定义标识符‘控件’。‘AbstractControl’不包含这样的成员”不确定它是否会在构建过程中通过 linter。
    • @Experimenter 感谢您指出这些事情!自 2018 年以来发生了很多变化,所以这个答案需要更新!答案和演示都更新了,我做了很多更改,所以筛选代码。嵌套数组的类型检查很棘手,因为您不能像外部 formarray(公司)那样使用 getter,在这种情况下需要一种解决方法......逃避类型检查或调用函数。你需要选择你想如何做到这一点。我已经在我的回答中逃避了类型检查,因为我不喜欢在模板中调用函数的想法......
    【解决方案2】:

    这就是我所做的 我使用了 angularflexlayout 和 angular material,你可以使用任何库,只是想向你展示功能

         <form [formGroup]="editForm" novalidate fxLayout="column"
            autocomplete="off">
    
        <div fxLayout="row wrap" fxLayoutGap="2em">
          <mat-form-field [fxFlex]="30">
            <mat-label>Name</mat-label>
            <input matInput formControlName="name" width="800px"/>
          </mat-form-field>
    
        </div>
        <div fxLayout="column" fxLayoutGap="3em">
          <div fxLayout="row wrap" fxFlex="40" fxLayoutGap="2em">
            <div formArrayName="phones" fxFlex="50" fxLayoutGap="8px" *ngFor="let phone of Phones.controls;let i= index">
              <mat-form-field fxFlex="100" [formGroupName]="i">
                <mat-label>Phone</mat-label>
                <input matInput formControlName="phone"/>
              </mat-form-field>
            </div>
            <button type="button" mat-stroked-button color="primary" (click)="addPhone()">add</button>
          </div>
          <div fxLayout="row wrap" fxFlex="40" fxLayoutGap="2em">
            <div formArrayName="emails" fxFlex="50" fxLayoutGap="8px" *ngFor="let email of Emails.controls;let i= index">
              <mat-form-field fxFlex="100" [formGroupName]="i">
                <mat-label>Email</mat-label>
                <input matInput formControlName="email"/>
              </mat-form-field>
            </div>
            <button type="button" mat-stroked-button color="primary" (click)="addEmail()">add</button>
          </div>
        </div>
    
        <div class="mr-2" fxLayoutAlign="end" mat-dialog-actions>
          <button type="button" (click)="cancelDialog()" mat-button
                  mat-dialog-close>Cancel
          </button>
    
          <button type="button" (click)="onSubmit()"
                  mat-raised-button
                  color="primary">
            Submit
          </button>
        </div>
    
      </form>
    

    然后是角度控制器

      editForm: FormGroup;
      phones: FormArray;
      emails: FormArray;
    
      createForm() {
        this.editForm = this.fb.group({
          name: [''],
            phones: this.fb.array([this.createPhone()]),
          emails: this.fb.array([this.createEmail()]),
         });
    
      }
    
      get Phones() {
        return this.editForm.get('phones') as FormArray;
      }
    
      get Emails() {
        return this.editForm.get('emails') as FormArray;
      }
    
     createPhone() {
        return this.fb.group(({
          phone: '',
        }));
      }
    
      createEmail() {
        return this.fb.group(({
          email: ''
        }));
      }
    
    
      addPhone(): void {
        this.phones = this.editListingForm.get('phones') as FormArray;
        this.phones.push(this.createPhone());
      }
    
      addEmail(): void {
        this.emails = this.editListingForm.get('emails') as FormArray;
        this.emails.push(this.createEmail());
      }
    

    【讨论】:

      【解决方案3】:

                          <tr formArrayName="entries"
                              *ngFor="let field of entriesGroup.get('entries').controls; let ind1 = index;">
                              <td [formGroupName]="ind1">
                                  <input type="text" disabled formControlName="date1" name="date1"
                                      class="form-control">
                              </td>
      
      
                              <td [formGroupName]="ind1" *ngIf="hideHeaders">
                                  <div formGroupName="odometerReadingDetails">
                                      <input type="text" formControlName="startingLocation" name="startingLocation"
                                          class="form-control">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="hideHeaders">
                                  <div formGroupName="odometerReadingDetails">
                                      <input type="text" formControlName="endingLocation" name="endingLocation"
                                          class="form-control">
                                  </div>
                              </td>
      
      
                              <td [formGroupName]="ind1" *ngIf="hideHeaders">
                                  <div formGroupName="odometerReadingDetails">
                                      <input type="text" formControlName="odometerReadStartingPoint"
                                          name="odometerReadStartingPoint" class="form-control"
                                          [ngModel]="ind1!=0?entriesGroup.get('entries').controls[ind1-1].get('odometerReadingDetails').get('odometerReadEndingPoint').value:field.get('odometerReadingDetails').get('odometerReadStartingPoint').value">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="hideHeaders">
                                  <div formGroupName="odometerReadingDetails">
                                      <input type="text" formControlName="odometerReadEndingPoint"
                                          name="odometerReadEndingPoint" class="form-control"
                                          [ngModel]="ind1<entriesGroup.get('entries').controls.length-1?entriesGroup.get('entries').controls[ind1+1].get('odometerReadingDetails').get('odometerReadStartingPoint').value:field.get('odometerReadingDetails').get('odometerReadEndingPoint').value">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="hideHeaders">
                                  <div formGroupName="odometerReadingDetails">
                                      <input type="text" disabled formControlName="odometerReadForOfficial"
                                          name="odometerReadForOfficial" class="form-control"
                                          [ngModel]="field.get('odometerReadingDetails').get('totalKilometersCovered').value-field.get('odometerReadingDetails').get('odometerReadForPersonal').value">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="hideHeaders">
                                  <div formGroupName="odometerReadingDetails">
                                      <input type="text" formControlName="odometerReadForPersonal"
                                          name="odometerReadForPersonal" class="form-control">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="hideHeaders">
                                  <div formGroupName="odometerReadingDetails">
                                      <input type="text" disabled formControlName="totalKilometersCovered"
                                          name="totalKilometersCovered" class="form-control"
                                          [ngModel]="field.get('odometerReadingDetails').get('odometerReadEndingPoint').value!=0?field.get('odometerReadingDetails').get('odometerReadEndingPoint').value-field.get('odometerReadingDetails').get('odometerReadStartingPoint').value:0">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="hideHeaders">
                                  <div formGroupName="odometerReadingDetails">
                                      <textarea rows="2" cols="20" type="text" formControlName="particularTravel"
                                          name="particularTravel" class="form-control">
      
                               </textarea>
                                  </div>
                              </td>
      
      
      
                              <td [formGroupName]="ind1" *ngIf="fuelHide">
                                  <div formGroupName="fuelDetails">
                                      <input type="text" formControlName="fuelFilled" name="fuelFilled"
                                          class="form-control">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="fuelHide">
                                  <div formGroupName="fuelDetails">
                                      <input type="text" formControlName="costPerLiter" name="costPerLiter"
                                          class="form-control">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="fuelHide">
                                  <div formGroupName="fuelDetails">
                                      <input type="text" disabled formControlName="costOfTheFuel" name="costOfTheFuel"
                                          class="form-control"
                                          [ngModel]="field.get('fuelDetails').get('costPerLiter').value * field.get('fuelDetails').get('fuelFilled').value">
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="fuelHide">
                                  <div formGroupName="fuelDetails">
                                      <input type="text" disabled formControlName="fuelConsumption"
                                          name="fuelConsumption" class="form-control"
                                          [ngModel]="field.get('fuelDetails').get('costOfTheFuel').value!=0?(ind1!=0?(entriesGroup.get('entries').controls[ind1-1].get('fuelDetails').get('fuelConsumption').value + field.get('fuelDetails').get('costOfTheFuel').value):field.get('fuelDetails').get('costOfTheFuel').value):0">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="NoNeed">
                                  <div formGroupName="fuelDetails">
                                      <input type="text" formControlName="requestId" name="requestId"
                                          class="form-control">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="fuelHide">
                                  <div formGroupName="fuelDetails">
                                      <input type="text" formControlName="couponCode" name="couponCode"
                                          class="form-control">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="hideDate">
                                  <div formGroupName="fuelDetails">
                                      <input type="text" formControlName="couponDate" name="couponDate"
                                          placeholder="date1,date2,..." class="form-control">
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="fuelHide">
                                  <div formGroupName="fuelDetails">
                                      <input type="text" formControlName="couponNumber" name="couponNumber"
                                          placeholder="coupon1,coupon2,..." class="form-control">
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="hideDate">
                                  <div formGroupName="fuelDetails">
                                      <input type="text" formControlName="couponsAmount" name="couponsAmount"
                                          class="form-control">
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="repaireHide">
                                  <div formGroupName="vehicleRepairDetails">
                                      <input type="text" formControlName="totalKmsCoveredBeforeRepair"
                                          name="totalKmsCoveredBeforeRepair" class="form-control" readonly
                                          [ngModel]="field.get('odometerReadingDetails').get('totalKilometersCovered').value">
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="repaireHide">
                                  <div formGroupName="vehicleRepairDetails">
                                      <input type="text" formControlName="sparesCost" name="sparesCost"
                                          class="form-control">
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="repaireHide">
                                  <div formGroupName="vehicleRepairDetails">
                                      <input type="text" formControlName="labourCost" name="labourCost"
                                          class="form-control">
                                  </div>
                              </td>
      
      
                              <td [formGroupName]="ind1" *ngIf="repaireHide">
                                  <div formGroupName="vehicleRepairDetails">
                                      <input type="text" formControlName="vehicleRepaireCost"
                                          name="vehicleRepaireCost" class="form-control"
                                          [ngModel]="field.get('vehicleRepairDetails').get('sparesCost').value*1 + field.get('vehicleRepairDetails').get('labourCost').value*1">
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="repaireHide">
                                  <div formGroupName="vehicleRepairDetails">
      
                                      <textarea rows="2" cols="20" type="text" formControlName="particularsOfRepairs"
                                          name="particularsOfRepairs" class="form-control">
      
                                 </textarea>
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="workShop">
                                  <div formGroupName="vehicleRepairDetails">
                                      <select formControlName="workshopId" class="form-control"
                                          (click)="workshopDetails1($event,ind1)">
      
                                          <option *ngFor="let i of workshopdata"
                                              [selected]="i.workShopId==field.get('vehicleRepairDetails').get('workshopId').value">
                                              {{i.workShopId}}</option>
                                          <option value="others">OTHERS</option>
                                      </select>
                                  </div>
                              </td>
      
      
                              <td [formGroupName]="ind1" *ngIf="workShop">
                                  <div formGroupName="vehicleRepairDetails">
                                      <textarea rows="2" cols="50" type="text" formControlName="workshopAddress"
                                          name="workshopAddress" class="form-control">
                 </textarea>
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="workShop">
                                  <div formGroupName="vehicleRepairDetails">
                                      <input type="text" formControlName="workshopContactNumber"
                                          name="workshopContactNumber" class="form-control">
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="workShop">
                                  <div formGroupName="vehicleRepairDetails">
      
                                      <textarea rows="2" cols="50" type="text"
                                          formControlName="workshopAccountDetails" name="workshopAccountDetails"
                                          class="form-control">
                 </textarea>
                                  </div>
                              </td>
                              <td [formGroupName]="ind1" *ngIf="repaireHide">
                                  <div formGroupName="vehicleRepairDetails">
                                      <input type="text" formControlName="requestId" name="requestId"
                                          class="form-control">
                                  </div>
                              </td>
      
      
      
      
                              <td [formGroupName]="ind1" *ngIf="driverHide">
                                  <div formGroupName="driverChargesDetails">
                                      <select formControlName="categoryType" class="form-control"
                                          (click)="onCatType1($event,ind1)">
                                          <option>Choose</option>
                                          <option *ngFor="let i of driverChargesLimit"
                                              [selected]="i.categoryType==field.get('driverChargesDetails').get('categoryType').value">
                                              {{i.categoryType}}</option>
      
                                      </select>
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="driverHide">
                                  <div formGroupName="driverChargesDetails">
                                      <input type="text" formControlName="amount" name="amount" class="form-control"
                                          disabled>
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="driverHide">
                                  <div formGroupName="driverChargesDetails">
                                      <input type="time" formControlName="startingTime" name="startingTime"
                                          class="form-control" (change)="onStartTime1($event,ind1)">
                                  </div>
                              </td>
      
                              <td [formGroupName]="ind1" *ngIf="driverHide">
                                  <div formGroupName="driverChargesDetails">
                                      <input type="time" formControlName="endingTime"
                                          (change)="onEndTime1($event,ind1)" name="endingTime" class="form-control">
                                  </div>
                              </td>
      
                          </tr>
      
                      </tbody>
                  </div>
              </table>
      

      【讨论】:

      • 如果它包含 8 个对象数据,它的重复很好。但如果它包含 10 个及以上,它不重复如何解决...?
      猜你喜欢
      • 1970-01-01
      • 2019-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-12
      • 1970-01-01
      相关资源
      最近更新 更多