【问题标题】:Angular Material - App crashes after using *ngFor on a FormArray for expansion panelsAngular Material - 在 FormArray 上为扩展面板使用 *ngFor 后应用程序崩溃
【发布时间】:2019-12-14 06:08:39
【问题描述】:

首先感谢@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;
    }

}

这是构建表单的组件的 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');
  }
}

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

【问题讨论】:

  • 由于 http 请求,stackblitz 不起作用。还有很多代码。您应该隔离您面临的具体问题,并通过创建minimal reproducible example 来表明这一点。太好了,您提供了一个堆栈闪电战,但如果它没有显示您遇到的问题,那它几乎没用;)
  • 正如我所担心的那样,尝试在更简单的示例上重现问题并不会重现错误...:stackblitz.com/edit/angular-42gobh
  • 好的,那还不错。现在逐个添加更多代码,直到您可以重现相同的错误。我很乐意看一下,其他人可能也是……但是代码太多了,我认为没有人会再看一眼,甚至找出这些代码从哪里来-ps 在您最初提出的堆栈闪电战中,太多了。不要刻意刻薄。
  • 会的。感谢您的跟进。当我崩溃时,我会告诉你的! ;)
  • 完成! :P ...添加角度材料(扩展面板)就像在我的应用程序中一样破坏了一切...(显然)我留下了&lt;div [formGroupName]="i"&gt;评论(使代码中断的部分),以便您了解它应该如何工作将其注释掉以使浏览器崩溃...

标签: angular angular-material ngfor formarray formgroups


【解决方案1】:

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

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

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

我注意到迭代值似乎在没有手风琴的情况下工作,所以我猜这也与这里的子组件方面有关,其中不断的重新渲染导致它耗尽内存并崩溃。可能值得在他们的 github 上注册,因为他们至少应该记录您不应该尝试这样做。

我继续添加了一个测试指令,该指令将它的构造函数登录到我按值迭代的一些普通 div 中,其中包含和不包含表单组名称指令。我发现,其中 n = 表单数组的长度,该指令将在没有 formGroupName 指令的情况下实例化 n*2 次。但是使用 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
  })

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

相比的主要好处

【讨论】:

  • 嗨,布莱恩!感谢您的回答。让我检查一下并验证您的答案!
  • 乐于助人
  • 我很好奇,所以我添加了一个测试指令,该指令在构造到一些普通的旧 div 时记录日志,这些 div 由内部和外部的 formGroupName 值迭代。使用 formGroupName,它会被实例化 n^2 + n 次,如果没有,它会被实例化 n*2 次,其中 n 是表单数组的长度。所以是的,这绝对是导致您的应用程序崩溃的原因,但仍然不确定发生这种情况的确切原因。在内部添加 formGroupName 指令的一些怪癖会导致值以指数方式重新评估
  • n^2+2n*** 次
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-05
  • 2018-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-05
相关资源
最近更新 更多