Angular2:更新数组时* ngFor不会更新
Angular2: *ngFor does not update when array is updated
我有一个对象数组(我们称它为 arr
)。在 (change)
方法中我的组件输入之一中,我修改了这些对象的属性之一,但在视图 (*ngFor
) 中没有任何变化。我读到 Angular2 变化检测不检查数组或对象的内容,所以我尝试了这些:
this.arr = this.arr.slice();
和
this.arr = [...this.arr];
但是视图没有改变,它仍然显示旧属性。在带有 console.log()
的 (change)
方法中,我得到了正确的数组。很奇怪,但这一个有效:this.arr = [];
我也尝试了 NgZone
和 markForCheck()
。
尝试创建深拷贝
this.arr = Object.assign({}, NEW_VALUE);
- 检查您的组件是否配置了 changeDetection:cHangeDetectionStrategy.OnPush,如果您要这样做,那么在更新数组后您必须调用 changeDetectorRef.markForCheck()
- 你也可以在这个函数中实现onChange生命周期钩子,改变数组的值。
您还可以在 *ngFor
表达式中使用 trackBy
选项,为数组中的每个项目提供唯一 ID。这确实让你 100% 负责变化检测,所以每次数组中的项目发生变化时更新这个(唯一的)属性。 Angular 将仅在数组中的任何项目被赋予不同的 trackBy
属性:
时才重新呈现列表
*ngFor="let item of (itemList$ | async); trackBy: trackItem"
或:
*ngFor="let item of itemList; trackBy: trackItem"
其中:
trackItem
是组件中的一个 public 方法:
public trackItem (index: number, item: Item) {
return item.trackId;
}
我通过在 @component 上添加一个 changDetection 指令解决了这个错误,如下所示
@Component({
selector: 'app-page',
templateUrl: './page.component.html',
styleUrls: ['./page.component.scss'],
changeDetection: ChangeDetectionStrategy.Default
})
您还需要导入它
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
有两种策略onPush和Default
onPush 使用 CheckOnce 策略,这意味着自动更改检测被停用,直到通过将策略设置为默认 (CheckAlways) 重新激活。仍然可以显式调用更改检测。
而默认使用 CheckAlways 策略,其中更改检测是自动的,直到明确停用。
来源Docs
既然你提到你已经尝试过 markForCheck()
,你应该试试 detectChanges(这对我有用,而 markForCheck 没用)。对于那些需要步骤的人:
将 ChangeDetectorRef 添加到您的导入:
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
将 ChangeDetectorRef 添加到您的构造函数中:
constructor(
private changeDetection: ChangeDetectorRef
) { }
更新数组后的下一行:
this.changeDetection.detectChanges();
万一其他人遇到我的情况,请确保正在更新的组件与正在渲染的组件是同一副本。
我使用 @ViewChild(MyComponent, { static: true }) private test: MyComponent
通过 ngfor 将数据传递给组件。 (在我的情况下,最终锁定到另一个我不知道的副本)
我可以通过将属性 #mycomp
添加到我在 html 中的组件标签并将上面的内容更改为 @ViewChild('mycomp', { static: true }) private test: MyComponent
来修复它
游戏迟到了,但使用 lodash 创建深度克隆对我有用
this.arr = cloneDeep(this.arr);
不是完美的解决方案,但有效:
const temp = this.arr;
this.arr = [];
temp.push(obj); // or any other changes
this.arr = temp;
我真的不喜欢导入新东西,因为会增加包的大小,在某些情况下会导致内存问题,所以我这样处理。
如果您重新分配数组,更改检测将不会注意到更改。因此,不要像 this.arr = ...
这样重新分配数组,而是直接更改数组。这可能看起来像这样:
this.arr.splice(0); // empty the array, without reassigning it
this.arr.push(...); // push new items
这样您就不需要手动调用更改检测。
例子
假设有一个 table 的列表,可以过滤(用户可以即搜索)。所以我将有一个包含所有可能项目的 list
,以及一个在 UI 中使用并显示给用户的 filteredList
。
// in ngOnInit I load the full list
this.list = this.myService.getList();
this.filteredList = [...this.list] // Since I will change the filtered list directly instead of reassigning it, I need to make a copy to not affect the original full list.
// then in a filter function, i.e. filterList()
this.filteredList.splice(0);
this.filteredList.push([...this.list.filter(/* ... */)]); // Again I need to push copies
每当我需要处理需要更改检测的 *ngFor 时,我更喜欢使用 behaviorSubject 和异步管道。
(我知道这是一个更长的解决方案。使用行为主题可以很容易地进行变化检测)
array$ = new BehaviorSubject([]);//Declare your array
当您需要更新数组时
array$.next(newArray)://Pass in new array data
如果您想读取 .ts 文件中的数组值,请使用此
array$.getValue()
在您的 .HTML 文件中
*ngFor='let obj of (array$ | async)'
我能够使用 ngZone
更新组件
export class comp {
arr: type[] = []
constructor (
private zone: NgZone
) {}
updateArr(data: type) {
this.arr.push(this.zone.run(() => data))
}
}
我有一个对象数组(我们称它为 arr
)。在 (change)
方法中我的组件输入之一中,我修改了这些对象的属性之一,但在视图 (*ngFor
) 中没有任何变化。我读到 Angular2 变化检测不检查数组或对象的内容,所以我尝试了这些:
this.arr = this.arr.slice();
和
this.arr = [...this.arr];
但是视图没有改变,它仍然显示旧属性。在带有 console.log()
的 (change)
方法中,我得到了正确的数组。很奇怪,但这一个有效:this.arr = [];
我也尝试了 NgZone
和 markForCheck()
。
尝试创建深拷贝
this.arr = Object.assign({}, NEW_VALUE);
- 检查您的组件是否配置了 changeDetection:cHangeDetectionStrategy.OnPush,如果您要这样做,那么在更新数组后您必须调用 changeDetectorRef.markForCheck()
- 你也可以在这个函数中实现onChange生命周期钩子,改变数组的值。
您还可以在 *ngFor
表达式中使用 trackBy
选项,为数组中的每个项目提供唯一 ID。这确实让你 100% 负责变化检测,所以每次数组中的项目发生变化时更新这个(唯一的)属性。 Angular 将仅在数组中的任何项目被赋予不同的 trackBy
属性:
*ngFor="let item of (itemList$ | async); trackBy: trackItem"
或:
*ngFor="let item of itemList; trackBy: trackItem"
其中:
trackItem
是组件中的一个 public 方法:
public trackItem (index: number, item: Item) {
return item.trackId;
}
我通过在 @component 上添加一个 changDetection 指令解决了这个错误,如下所示
@Component({
selector: 'app-page',
templateUrl: './page.component.html',
styleUrls: ['./page.component.scss'],
changeDetection: ChangeDetectionStrategy.Default
})
您还需要导入它
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
有两种策略onPush和Default
onPush 使用 CheckOnce 策略,这意味着自动更改检测被停用,直到通过将策略设置为默认 (CheckAlways) 重新激活。仍然可以显式调用更改检测。
而默认使用 CheckAlways 策略,其中更改检测是自动的,直到明确停用。
来源Docs
既然你提到你已经尝试过 markForCheck()
,你应该试试 detectChanges(这对我有用,而 markForCheck 没用)。对于那些需要步骤的人:
将 ChangeDetectorRef 添加到您的导入:
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
将 ChangeDetectorRef 添加到您的构造函数中:
constructor(
private changeDetection: ChangeDetectorRef
) { }
更新数组后的下一行:
this.changeDetection.detectChanges();
万一其他人遇到我的情况,请确保正在更新的组件与正在渲染的组件是同一副本。
我使用 @ViewChild(MyComponent, { static: true }) private test: MyComponent
通过 ngfor 将数据传递给组件。 (在我的情况下,最终锁定到另一个我不知道的副本)
我可以通过将属性 #mycomp
添加到我在 html 中的组件标签并将上面的内容更改为 @ViewChild('mycomp', { static: true }) private test: MyComponent
游戏迟到了,但使用 lodash 创建深度克隆对我有用
this.arr = cloneDeep(this.arr);
不是完美的解决方案,但有效:
const temp = this.arr;
this.arr = [];
temp.push(obj); // or any other changes
this.arr = temp;
我真的不喜欢导入新东西,因为会增加包的大小,在某些情况下会导致内存问题,所以我这样处理。
如果您重新分配数组,更改检测将不会注意到更改。因此,不要像 this.arr = ...
这样重新分配数组,而是直接更改数组。这可能看起来像这样:
this.arr.splice(0); // empty the array, without reassigning it
this.arr.push(...); // push new items
这样您就不需要手动调用更改检测。
例子
假设有一个 table 的列表,可以过滤(用户可以即搜索)。所以我将有一个包含所有可能项目的 list
,以及一个在 UI 中使用并显示给用户的 filteredList
。
// in ngOnInit I load the full list
this.list = this.myService.getList();
this.filteredList = [...this.list] // Since I will change the filtered list directly instead of reassigning it, I need to make a copy to not affect the original full list.
// then in a filter function, i.e. filterList()
this.filteredList.splice(0);
this.filteredList.push([...this.list.filter(/* ... */)]); // Again I need to push copies
每当我需要处理需要更改检测的 *ngFor 时,我更喜欢使用 behaviorSubject 和异步管道。 (我知道这是一个更长的解决方案。使用行为主题可以很容易地进行变化检测)
array$ = new BehaviorSubject([]);//Declare your array
当您需要更新数组时
array$.next(newArray)://Pass in new array data
如果您想读取 .ts 文件中的数组值,请使用此
array$.getValue()
在您的 .HTML 文件中
*ngFor='let obj of (array$ | async)'
我能够使用 ngZone
更新组件export class comp {
arr: type[] = []
constructor (
private zone: NgZone
) {}
updateArr(data: type) {
this.arr.push(this.zone.run(() => data))
}
}