Angular2: *ngFor не обновляется при обновлении массива
У меня есть массив объектов (давайте назовем его arr
). В одном из входов моего компонента в (change)
метод я изменяю один из атрибутов этих объектов, но в представлении (*ngFor
) ничего не меняется. Я прочитал, что обнаружение изменений Angular2 не проверяет содержимое массивов или объектов, поэтому я попробовал это:
this.arr = this.arr.slice();
а также
this.arr = [...this.arr];
Но представление не меняется, оно все еще показывает старый атрибут. в (change)
метод с console.log()
Я получил правильный массив. Странно, но этот работает: this.arr = [];
Я старался NgZone
а также markForCheck()
тоже.
11 ответов
Попробуйте создать глубокую копию, выполнив
this.arr = Object.assign({}, NEW_VALUE);
Так как вы упомянули, что вы уже пытались markForCheck()
, вместо этого вы должны попытаться обнаружить Detection (это как раз то, что у меня сработало, а markForCheck не сработало). Для тех, кому нужны шаги:
Добавьте ChangeDetectorRef в ваш импорт:
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
Добавьте ChangeDetectorRef в ваш конструктор:
constructor(
private changeDetection: ChangeDetectorRef
) { }
Затем на следующей строке после обновления массива:
this.changeDetection.detectChanges();
Вы также можете использовать trackBy
вариант в вашем *ngFor
выражение, предоставляя уникальный идентификатор для каждого элемента в массиве. Это делает вас на 100% ответственным за обнаружение изменений, поэтому обновляйте это (уникальное) свойство каждый раз, когда изменяется элемент в массиве. Затем Angular будет перерисовывать список только в том случае, если какой-либо элемент в вашем массиве получил другой trackBy
имущество:
*ngFor="let item of (itemList$ | async); trackBy: trackItem"
или же:
*ngFor="let item of itemList; trackBy: trackItem"
где:
trackItem
это публичный метод в вашем компоненте:
public trackItem (index: number, item: Item) {
return item.trackId;
}
Поздно в игре, но создание глубокого клона с использованием lodash сработало для меня
this.arr = cloneDeep(this.arr);
Я решил эту ошибку, добавив директиву changDetection в @component следующим образом
@Component({
selector: 'app-page',
templateUrl: './page.component.html',
styleUrls: ['./page.component.scss'],
changeDetection: ChangeDetectionStrategy.Default
})
Вам также нужно импортировать его
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
Есть две стратегии на Push и Default
OnPush использует стратегию CheckOnce, что означает, что автоматическое обнаружение изменений деактивируется до тех пор, пока не будет активировано снова, если для стратегии задано значение Default (CheckAlways). Обнаружение изменений все еще может быть вызвано явно.
в то время как Default использует стратегию CheckAlways, в которой обнаружение изменений происходит автоматически до явной деактивации.
Исходные документы
- Проверьте, настроен ли ваш компонент с помощью changeDetection:cHangeDetectionStrategy.OnPush, если вы собираетесь это сделать, то после обновления массива вы должны вызвать changeDetectorRef.markForCheck()
- Вы также можете реализовать хук жизненного цикла onChange и изменить значения массива внутри этой функции.
Мне удалось обновить компонент с помощью ngZone
export class comp {
arr: type[] = []
constructor (
private zone: NgZone
) {}
updateArr(data: type) {
this.arr.push(this.zone.run(() => data))
}
}
Если вы переназначаете массив, обнаружение изменений не заметит изменения. Поэтому вместо переназначения массива вроде
this.arr = ...
изменить массив напрямую. Это могло выглядеть так:
this.arr.splice(0); // empty the array, without reassigning it
this.arr.push(...); // push new items
Таким образом, вам не нужно вызывать обнаружение изменений вручную.
Пример
Скажем, у вас есть список для таблицы, которую можно фильтровать (пользователь может выполнять поиск). Так что у меня будет
list
который содержит все возможные элементы, и
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
Не идеальное решение, но работает:
const temp = this.arr;
this.arr = [];
temp.push(obj); // or any other changes
this.arr = temp;
Мне действительно не нравится импортировать новый материал из-за увеличения размера пакета, а в некоторых случаях это может вызвать проблемы с памятью, поэтому я справляюсь с этим таким образом.
Если кто-то еще столкнется с моей ситуацией, убедитесь, что обновляемый компонент является той же копией, что и визуализируемый.
Я использовал @ViewChild(MyComponent, { static: true }) private test: MyComponent
для передачи данных в компонент с помощью ngfor. (В моем случае это привело к блокировке другой копии, о которой я не знал)
Мне удалось исправить это, добавив атрибут #mycomp
в мой тег компонента в html и изменив приведенное выше на @ViewChild('mycomp', { static: true }) private test: MyComponent
Каждый раз, когда мне нужно иметь дело с *ngFor, который требует обнаружения изменений, я предпочитаю использовать behaviorSubject и async pipe. (Я знаю, что это более долгое решение. Использование предмета поведения упрощает работу с обнаружением изменений)
array$ = new BehaviorSubject([]);//Declare your array
Когда вам нужно обновить свой массив
array$.next(newArray)://Pass in new array data
если вы хотите прочитать значение вашего массива в файле .ts, используйте это
array$.getValue()
в вашем .HTML файле
*ngFor='let obj of (array$ | async)'