[英]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.