【问题标题】:RangeError: Maximum call stack size exceeded in AngularRangeError:Angular 中超出了最大调用堆栈大小
【发布时间】:2021-09-07 17:04:32
【问题描述】:

我在我的 Angular 应用程序类型的 stackOverflow 上遇到错误。 (见最后更新)

基本上我的组件是一个包含两个组件的图表。 equipment 组件包含设备信息,socket 组件包含每个设备的连接类型。套接字组件基本上显示了一个下拉列表,其中包含可连接的设备。 “容器”组件基本上创建了一个类型为“起始元素”的套接字组件,因此我们有一个“假”连接来选择起始元素。从那时起,我们选择一个起始元素,我们可以继续输入图表。 当我选择第一个“开始元素”(pic1)时,相应的设备将加载其相应的插座(7 个单元)。然后我在其中一个插座上选择了一个设备(pic2),同样,它装载了一个单元,一切都很好。然后我在相应的套接字(pic3)中再选择一个设备,然后,应用程序崩溃了。

我收到此错误:

ERROR RangeError: Maximum call stack size exceeded
    at refreshView (core.js:9461)
    at refreshEmbeddedViews (core.js:10591)
    at refreshView (core.js:9490)
    at refreshComponent (core.js:10637)
    at refreshChildComponents (core.js:9263)
    at refreshView (core.js:9516)
    at refreshEmbeddedViews (core.js:10591)
    at refreshView (core.js:9490)
    at refreshComponent (core.js:10637)
    at refreshChildComponents (core.js:9263)

我制作了一些控制台日志,在发生错误的单击处,第一个开始元素被呈现(ngOnInit),第二个也是如此,在我单击的第三个中,Socket onInit 和 Equipment onInit 被调用没有结束。 看来设备和插座都在不停地调用自己。

我检查了我的应用程序,我只调用了两次套接字组件。一旦在容器组件上创建“开始元素”套接字,然后在设备组件上的 ngfor 上。而关于设备,它在整个应用程序中只调用一次,一旦mySocket.element被选中并且不为空,因此ngIf显示组件。

我只有一个模块app.module,所以这里一样,没有对组件的多次调用。

这里是socket组件:

import { Component, Input, OnInit } from '@angular/core';
import { Equipment } from 'src/app/model/Equipment';
import { Socket } from 'src/app/model/Socket';
import { EquipmentService } from 'src/app/services/equipment.service';

@Component({
  selector: 'app-socket',
  templateUrl: './socket.component.html',
  styleUrls: ['./socket.component.css']
})
export class SocketComponent implements OnInit {

  @Input() mySocket: Socket;
  @Input() parentEquipment: Equipment;

  myElements: Equipment[];

  constructor( private _equipmentNewService:EquipmentService) { }

  ngOnInit(): void {
    console.log("Myparentequipment:", this.parentEquipment)
    this.myElements = this._equipmentNewService.getListForGivenInput(this.mySocket.type);
  }

  loadEquipment(eq:Equipment){
    this.mySocket.element = eq;
  }

  amIFirstConnection(){
    return !this.amIOnlyConnection() && this.mySocket === this.parentEquipment.eqType.connections[0];
  }

  amILastConnection(){
    return !this.amIOnlyConnection() && this.mySocket === this.parentEquipment.eqType.connections[this.parentEquipment.eqType.connections.length-1];
  }

  amIMiddleConnection(){
    return !this.amIOnlyConnection() && !this.amIFirstConnection() && !this.amILastConnection();
  }

  amIOnlyConnection(){
    return this.parentEquipment.eqType.connections.length===1
  }

}

这里是html文件:

<!-- this is just to create the diagram lines, ignore it! -->
<div *ngIf="mySocket.type !== 'Start Element'">
    <div *ngIf="amIOnlyConnection()" class="d-flex someMarginTop">
        <div class="smallSpacer schema"></div>
        <div class="mainSpacer schema borderLeft"></div>
    </div>
    <div *ngIf="amIFirstConnection()" class="d-flex someMarginTop">
        <div class="smallSpacer schema"></div>
        <div class="mainSpacer schema borderLeft borderTop"></div>
    </div>
    <div *ngIf="amIMiddleConnection()" class="d-flex someMarginTop">
        <div class="smallSpacer schema borderTop"></div>
        <div class="mainSpacer schema borderTop borderLeft"></div>
    </div>
    <div *ngIf="amILastConnection()" class="d-flex someMarginTop">
        <div class="smallSpacer schema borderTop"></div>
        <div class="mainSpacer schema borderLeft"></div>
    </div>
</div>
<!-- here we display the info -->
<div class="dropdown someMargin">
    <button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
          {{mySocket.name}}...
        </button>
    <ul class="dropdown-menu listSocket" aria-labelledby="dropdownMenuButton1">
        <li *ngFor="let item of myElements">
            <div class="d-flex"><a class="dropdown-item" (click)="loadEquipment(item)"><span style="margin-right:5px">{{item.eqType.productNumber}}</span></a></div>
        </li>

    </ul>
    <!-- and here the call to equipment -->
    <div *ngIf="mySocket.element">
        <app-equipment [equipment]="mySocket.element" [parentSocket]="this.mySocket"></app-equipment>

    </div>
</div>

现在是设备组件:

import { Component, Input, OnInit } from '@angular/core';
import { Equipment } from 'src/app/model/Equipment';
import { Socket } from 'src/app/model/Socket';

@Component({
  selector: 'app-equipment',
  templateUrl: './equipment.component.html',
  styleUrls: ['./equipment.component.css']
})
export class EquipmentComponent implements OnInit {


  @Input() equipment: Equipment;
  @Input() parentSocket: Socket;

  constructor() {
   }

  ngOnInit(): void {
    console.log("Myparentsocket:", this.parentSocket)
    this.equipment.available = false;
  }

  removeMyselfFromSocket() {
    this.parentSocket.element = null;
  }

}

这里是html文件:

<div class="someMargin somePadding  col-md-auto border rounded eqContainer animated fadeIn fast">
    <div style="float:right">
        <button type="button" class="btn btn-outline-danger px-3" style="padding: 0px 7px !important;" (click)="removeMyselfFromSocket()"><i class="fas fa-trash fa-xs"></i></button>
    </div>
    <div class="d-flex">
        <div>
            <!-- <img [src]="equipment.eqType.img" class="img-fluid imgTestModel" [alt]="equipment.eqType.productNumber"> -->
        </div>
        <div style="margin-left:5px" class="bg-light border rounded somePadding" style="margin-right:30px">
            <!-- <p class="title p-lessMargin text-primary">{{equipment.eqType.productNumber}}</p> -->
            <p class="ids p-lessMargin">ProductNO: <span class="ids-content">ABC8493RT</span> </p>
            <p class="ids p-lessMargin">SerialNO: <span class="ids-content">15698247896</span></p>
            <p class="ids p-lessMargin">SwRev: <span class="ids-content">2.2.145</span></p>
        </div>

    </div>
    <div class="d-flex">
        <app-socket *ngFor="let socket of equipment.eqType.connections" [mySocket]="socket" [parentEquipment]="equipment"></app-socket>
    </div>

</div>

我希望我附上了足够的信息,我很乐意得到一些帮助!

提前致谢:)

更新 1: 在套接字组件上更改此方法后(以避免始终调用更改检测器):

equipmentSelected:Equipment;
loadEquipment(eq:Equipment){
    this.equipmentSelected = eq;
  }

我现在可以在我的树中添加更多孩子。现在的问题是,我该怎么做才能保留this.mySocket.element = eq?否则我会丢失我的树形结构并且无法序列化我的对象...

【问题讨论】:

  • 识别问题并不容易。我看到两个主要的地方可能是原因:模板中的突变和函数不是那么容易被 Angular 变化检测器处理
  • 这不是一个简单而简短的话题。需要时间去理解。这篇文章会有所帮助:blog.angular-university.io/…
  • 非常感谢文章的链接!读完之后,我做了一个改动。在函数 loadEquipment() 中的 socket 组件中,而不是 this.mySocket.element = eq;我创建了一个新变量并这样做:this.equipmentSelected = eq;现在我需要弄清楚如何保留 this.mySocket.element 上的值,否则我会丢失树结构。你有什么想法吗?
  • 您有 2 个相互引用的组件。这几乎肯定是您出现堆栈溢出的原因。我会仔细看看您是如何构建应用程序的,并考虑进行重组以避免这种循环引用。
  • 所以最后在 loadEquipment() 中我只是使用了这样的对象的克隆:this.mySocket.element = JSON.parse(JSON.stringify(eq));现在它可以工作了。我不完全理解为什么它会这样工作,很高兴能从你们中的一个人那里得到一个很好的答案! :)

标签: javascript angular typescript


【解决方案1】:

在一位好朋友的帮助下,我找到了解决方案。 它位于套接字组件上的方法loadEquipment() 上。 当它更改在此组件上创建的对象 mySocket 时,该组件被再次渲染,并且由于它是来自父套接字的输入,父套接字设备组件也被再次渲染,从而在加载相互引用的组件时创建堆栈溢出整个时间。

我需要将方法更改为:

loadEquipment(eq:Equipment){
    // js workaround for cloning
    this.mySocket.element = JSON.parse(JSON.stringify(eq));
} 

创建对象的克隆可使更改检测器保持安静并避免重新渲染。

【讨论】:

    猜你喜欢
    • 2016-09-26
    • 1970-01-01
    • 2017-07-02
    • 2013-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多