Angular Material-在FormArray的扩展面板上使用* ngFor后,应用程序崩溃

Ard*_*zii 3 angular-material ngfor angular formarray formgroups

首先,感谢@ AJT_82提供的建议!

我一直在研究一个更简单的代码重现错误的示例,可以在stackblitz.com/edit/angular-42gobh中 找到该错误。注释了有问题的行,以便您可以检查出正确的结果。只需取消注释, <div [formGroup]="i"></div> 就可以使一切崩溃

基本上,我有一个为组件构建表单的服务,并且HTML文件使用Angular Material。当手风琴用于formArray时,应用程序完全崩溃,并且无法正确分配formGroup:

customer-edit.service.ts:

import { Injectable } from '@angular/core';
import { FormGroup, FormBuilder, FormArray, Validators } from '@angular/forms';

@Injectable({
  providedIn: 'root'
})
export class CustomerEditService {
  private cusForm: FormGroup = this.fb.group({
    thirdParty: this.fb.group({
      name: this.fb.control(null),
      vat: this.fb.control(null),
      corpoPhone: this.fb.control(null),
      corpoMail: this.fb.control(null),
      corpoWeb: this.fb.control(null),
      activityNumber: this.fb.control(null),
      addresses: this.fb.array([]),
      contacts: this.fb.array([])
    }),
    docRefs: this.fb.group({}),
    commentsArr: this.fb.group({})
  });

  constructor(
    private fb: FormBuilder
    ) {    }

    // **** EMPTY FORMS GETTERS ****

    getAddressForm(address?: any) {
      const addressForm: FormGroup = this.fb.group({
        street: this.fb.control(null),
        streetcomp: this.fb.control(null),
        streetcomp2: this.fb.control(null),
        city: this.fb.control(null),
        cp: this.fb.control(null),
        state: this.fb.control(null),
        country: this.fb.control(null),
        main: this.fb.control(null)
      });
      if (address) {
        addressForm.setValue(address);
      }
      return addressForm;
    }

    getFilledThirdPartyForm(thirdParty?: any) {
      const thirdPartyForm: FormGroup = this.fb.group({
        name: this.fb.control(null, Validators.required),
        vat: this.fb.control(null, Validators.required),
        corpoPhone: this.fb.control(null, Validators.required),
        corpoMail: this.fb.control(null, Validators.required),
        corpoWeb: this.fb.control(null, Validators.required),
        activityNumber: this.fb.control(null),
      });
      if (thirdParty) {
        Object.keys(thirdParty).map(
          el => {
            if (Object.keys(thirdPartyForm.controls).indexOf(el) !== -1 && el !== 'addresses') {
              thirdPartyForm.get(el).setValue(thirdParty[el]);
            }
        });
      }
      return thirdPartyForm;
    }

}
Run Code Online (Sandbox Code Playgroud)

这是构建表单的组件的TS文件。该表单基于“现实生活”中的JSON对象(第三方)构建,该对象通过HTTP请求来自数据库:

import { Component, OnInit, Input } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms';
import { CustomerEditService } from '../customer-edit.service';

@Component({
  selector: 'app-basic-edit',
  templateUrl: './test.component.html'
})

export class testComponent implements OnInit {
  thirdParty: any = 
    {
      "addresses": [
        {
          "street": "AVENIDA ESTADOS UNIDOS, 141",
          "streetcomp": "",
          "streetcomp2": "",
          "city": "SAN BARTOLOME DE TIRAJANA ",
          "cp": "35290",
          "state": "PALMAS (LAS)",
          "country": "spain",
          "main": true
        },
        {
          "street": "OTRA DIRECCION DUMMY",
          "streetcomp": "",
          "streetcomp2": "",
          "city": "MADRID",
          "cp": "280007",
          "state": "MADRID",
          "country": "spain",
          "main": false
        }
      ],
      "contacts": [
        {
          "_id": "5cf0f6f2a3e9cf847c5861af",
          "title": "Mrs.",
          "role": "CFO",
          "firstName": "John",
          "lastName": "Doe",
          "phone": "912345654",
          "mobile": "673369900",
          "thirdParty_id": "5cf0f6d0a3e9cf847c5861aa",
          "addresses": [
            {
              "street": "AVENIDA ESTADOS UNIDOS , 141",
              "streetcomp1": "TUNTE",
              "streetcomp2": "",
              "cp": "35290",
              "city": "SAN BARTOLOME DE TIRAJANA ",
              "state": "PALMAS (LAS)"
            }
          ],
          "email": "jdoe@ketchup.com",
          "auditTrail": {
            "creation": {
              "user_id": "1",
              "creationDate": "1559213796974"
            },
            "modification": [
              {
                "user_id": "1",
                "modifDate": "1559213833358"
              }
            ]
          }
        }
      ] 
    };

  thirdPartyForm: FormGroup;

  constructor(
    private fb: FormBuilder,
    private cusEditService: CustomerEditService
  ) {

  }

  ngOnInit() {
    this.thirdPartyForm = this.cusEditService.getFilledThirdPartyForm(this.thirdParty);
    const addresses: any[] = this.thirdParty.addresses;
    const addressesFormArr: FormArray = new FormArray([]);
    addresses.forEach(
      address => {
        const currAddressForm: FormGroup = this.cusEditService.getAddressForm(address);
        addressesFormArr.push(currAddressForm);
      });
    this.thirdPartyForm.setControl(
      'addresses',
      addressesFormArr
    );
    console.log(this.thirdPartyForm.get('addresses'));
  }

  onSubmit() {
    console.log('Submitted');
  }
}
Run Code Online (Sandbox Code Playgroud)

这是HTML:

<h1>Addresses Test</h1>
<form [formGroup]="thirdPartyForm" (ngSubmit)="onSubmit()">
  <div formArrayName="addresses">
    <mat-accordion>
      <mat-expansion-panel *ngFor="let address of thirdPartyForm.get('addresses').value; index as i">
        <mat-expansion-panel-header>
          {{ address.city }}
        </mat-expansion-panel-header>
          <div [formGroupName]="i"> <!-- UNCOMMENTING THIS LINE MAKES EVERYTHING CRASH -->
          </div>
      </mat-expansion-panel>
    </mat-accordion>
  </div>
</form>
Run Code Online (Sandbox Code Playgroud)

bry*_*n60 8

希望我能告诉您确切的原因,但是解决方案是将迭代更改为超出控件而不是值以使其起作用:

<mat-expansion-panel *ngFor="let address of thirdPartyForm.get('addresses').controls; index as i" >
    <mat-expansion-panel-header>
        {{ address.get('city').value }}
    </mat-expansion-panel-header>
    <div [formGroupName]="i">
        <input formControlName="city">
    </div> 
</mat-expansion-panel>
Run Code Online (Sandbox Code Playgroud)

我最好的猜测是,通过访问要迭代的值并尝试在其中嵌套一个表单组名称,您已经创建了某种循环,其中内容在访问formGroupName指令时导致内容重新呈现,从而导致值重新评估,而控件不要,因为它是静态属性。表单控件中的值实际上是真正的吸气剂,因此可以重新评估。函数迭代是不纯的,因此会不断对其进行重新评估,结果,在ngFor中,内容不断被重新呈现(缺少trackBy子句)。

我注意到迭代值似乎不需要手风琴就可以工作,因此我猜想这也与子组件方面有关,因为子组件方面不断的重新渲染会导致其耗尽内存并崩溃。可能值得在他们的github上注册它,因为他们至少应该证明您不应尝试这样做。

我继续并添加了一个测试指令,该指令将其构造函数登录到一些普通的div中,在其中按值进行迭代,无论是否带有表单组名指令。我发现,在n =表单数组的长度的情况下,该指令将实例化n * 2次,而内部没有formGroupName指令。但是使用formGroupName,它实例化了n ^ 2 + 2n次。因此,可以肯定的是,迭代值会导致它两次求值,然后由于某种原因添加该指令会导致它对顶部数组中的每个组再次求值,从而使内容呈指数增长。相反,使用控件进行迭代会使指令仅实例化n次。

不过,仍然不能完全确定为什么会发生这种情况,只需确定会发生这种情况并且是导致崩溃的原因即可。

演示闪电战:https ://stackblitz.com/edit/angular-fgqytp ? file = src/app/test-component/ test.component.html

不相关的旁注,您可以像这样使用formbuilder:

  const thirdPartyForm: FormGroup = this.fb.group({
    name: [null, Validators.required],
    vat: [null, Validators.required],
    corpoPhone: [null, Validators.required],
    corpoMail: [null, Validators.required],
    corpoWeb: [null, Validators.required],
    activityNumber: null
  })
Run Code Online (Sandbox Code Playgroud)

表单构建器中的“叶”值假定为控件,并且它接受该数组语法以根据需要添加验证器。这是使用formbuilder而不是仅仅这样做的主要好处new FormGroup({key: new FormControl(null)})