繁体   English   中英

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

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

我有一个非常简单的设置。 我在 app.component.html 中有一个输入字段,用户可以在其中输入字符串。 我将该字符串添加到 Map<string, number>。 我正在将该地图传递给子组件。 我正在使用 @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)

这几乎肯定是由在模板中使用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>

问题是 entry() 函数。 该函数在内部创建了一个可迭代对象,每次向其添加内容时它都会更改。 从组件视图调用函数被称为不好的做法,因此如果您想在父组件中维护 Map,那么我建议您子组件从父组件接收数组而不是 Map。 为此,您必须在父组件中创建一个数组属性作为 Map 并将其传递给子组件。 每次执行 updateName 函数时,您都应该同时维护 Map 和 Array。

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

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

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

然后,我们也必须更改子视图和父视图:

家长:

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

孩子:

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

因此,以这种方式,我们使用 Map 处理重复项,因此也在 Array 中处理重复项,因为我们依赖于 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();
              }
updateName(){

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM