Angular 使用 Map 将数据从父组件传递到子组件时出现 ExpressionChangedAfterItHasBeenCheckedError

Angular ExpressionChangedAfterItHasBeenCheckedError when passing data from Parent Component to Child Component using Map

我有一个非常简单的设置。 我在 app.component.html 中有一个输入字段,用户可以在其中输入字符串。我将该字符串添加到 Map。我将该映射传递给子组件。我正在使用 @Input() 访问子组件中的地图并对其进行迭代并显示子组件中的值。

app.component.html -> 父组件

<div class="shopping-cart">
    <app-shopping-cart-item [shoppingItems]="shoppingItems"></app-shopping-cart-item>
</div>
<p>
  <button class="btn btn-primary" (click)="updateName()">Update Name</button>
</p>

app.component.ts

  shoppingItems = new Map<string, number>();

  updateName(){
    this.shoppingItems.set('test', 1);
  }

购物车-component.html

<div *ngFor="let article of this.shoppingItems.entries()" class="shopping-cart-item">
  <div class="article-name">{{ article[0] }}</div>
</div>

购物车-Item.component.ts

  @Input()
  shoppingItems;

问题是当我在输入值(父级)中输入一个值并单击更新时,它会更新子组件中显示的值。但是我得到一个 ExpressionChangedAfterItHasBeenCheckedError。

我知道当我们没有单向数据流时会出现此错误。但是我不明白为什么在这种情况下会出现此错误。

core.js:6210 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '[object Map Iterator]'. Current value: '[object Map Iterator]'.
    at throwErrorIfNoChangesMode (core.js:8129)
    at bindingUpdated (core.js:19991)
    at Module.ɵɵproperty (core.js:21142)
    at ShoppingCartItemComponent_Template (shopping-cart-item.component.html:3)
    at executeTemplate (core.js:12059)
    at refreshView (core.js:11906)
    at refreshComponent (core.js:13358)
    at refreshChildComponents (core.js:11635)
    at refreshView (core.js:11958)
    at refreshComponent (core.js:13358)
updateName(){

setTimeout(() => {
this.shoppingItems.set('test', 1);   
},
 0);
     
}

试试这个,你应该不会再得到那个错误(将代码放在 setTimeout 的函数中)

这几乎肯定是模板中使用了entries()函数造成的。我无法具体说明为什么会导致这个问题,因为我不太了解 Map 的内部工作原理,但我假设它是一个新数组(或迭代器)在每次调用该函数时都会被创建,并且该函数在每个变化检测周期中都会被调用,这使得 angular 相信在按钮点击引起的变化检测周期中某些东西正在发生变化。或者 Angular 不能很好地处理迭代器。

无论哪种方式,使用 keyvalue 管道进行迭代应该可以解决问题,因为它只会在需要时更改数组:

<div *ngFor="let article of this.shoppingItems | keyvalue" class="shopping-cart-item">
  <div class="article-name">{{ article.key }}</div>
</div>

问题出在 entries() 函数上。该函数在内部创建了一个可迭代的 object ,每次您向其中添加内容时它都会发生变化。从组件视图调用函数是一种不好的做法,因此如果您想在 parent 组件中维护 Map 那么我建议您 child 组件从 parent 接收一个数组的地图。为此,您必须在 parent 组件中创建一个数组 属性 作为 Map 并将其传递给 child 组件。每次执行 updateName 函数时,您都应该同时维护 Map 和 Array。

  shoppingItems = new Map<string, number>();
  childArray = [];

  updateName(){
    this.shoppingItems.set('test', 1);
    this.childArray.push(this.shoppingItems.get('test'));
  }

一个小广告: 您的 updateName() 函数有一个重要的情况。首先,它不是参数化函数,因为每次执行它时都会添加相同的东西。因此,Map 是一种结构,不允许您多次添加相同的键,在本例中为 'test',因此每次都会替换相同的键值。除此之外,当您想参数化函数时,必须先确定该键是否已添加到数组中,以避免重复项并在适用时替换索引。 仅供参考。 那么,接下来。

然后,我们也必须更改 child 和 parent 视图:

Parent:

<div class="shopping-cart">
    <app-shopping-cart-item [shoppingItems]="childArray"></app-shopping-cart-item>
</div>
<p>
  <button class="btn btn-primary" (click)="updateName()">Update Name</button>
</p>

Child:

<div *ngFor="let article of this.shoppingItems" class="shopping-cart-item">
  <div class="article-name">{{ article }}</div>
</div>

因此,我们以这种方式使用 Map 处理重复项,因此也在数组中处理重复项,因为我们依赖于 Map。

试试吧。

注意:还有一个重要且高级的主题称为变化检测

Try like this. 

    import { Component, ElementRef, OnInit, ViewChild, ChangeDetectorRef, ViewRef } from '@angular/core';
    
      constructor(
        private dtr: ChangeDetectorRef,
      ) {
      }
// write this code where you want to detect change in browser
     if (this.dtr && !(this.dtr as ViewRef).destroyed) {
                this.dtr.detectChanges();
              }