У меня есть массив элементов, которые пользователь может не только редактировать, но также добавлять и удалять полные элементы массива. Это хорошо работает, если я не попытаюсь добавить значение в начало массива (например, используя unshift
).
Вот тест, демонстрирующий мою проблему:
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
@Component({
template: '
<form>
<div *ngFor="let item of values; let index = index">
<input [name]="'elem' + index" [(ngModel)]="item.value">
</div>
</form>'
})
class TestComponent {
values: {value: string}[] = [{value: 'a'}, {value: 'b'}];
}
fdescribe('ngFor/Model', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
let element: HTMLDivElement;
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [TestComponent]
});
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
fixture.detectChanges();
await fixture.whenStable();
});
function getAllValues() {
return Array.from(element.querySelectorAll('input')).map(elem => elem.value);
}
it('should display all values', async () => {
// evaluation
expect(getAllValues()).toEqual(['a', 'b']);
});
it('should display all values after push', async () => {
// execution
component.values.push({value: 'c'});
fixture.detectChanges();
await fixture.whenStable();
// evaluation
expect(getAllValues()).toEqual(['a', 'b', 'c']);
});
it('should display all values after unshift', async () => {
// execution
component.values.unshift({value: 'z'});
fixture.detectChanges();
await fixture.whenStable();
// evaluation
console.log(JSON.stringify(getAllValues())); // Logs '["z","z","b"]'
expect(getAllValues()).toEqual(['z', 'a', 'b']);
});
});
Первые два теста проходят отлично. Однако третий тест не пройден. В третьем тесте я пытаюсь добавить "z" к своим входам, что успешно, однако второй ввод также показывает "z", чего не должно быть.
(Обратите внимание, что в Интернете существуют сотни подобных вопросов, но в других случаях у людей просто не было уникальных name
-attributes, и они также просто добавляли, а не добавляли).
Почему это происходит и что я могу с этим сделать?
Примечания к trackBy
До сих пор ответы были просто "использовать trackBy". Однако документация для trackBy гласит:
По умолчанию детектор изменений предполагает, что экземпляр объекта идентифицирует узел в итерируемом
Поскольку я не предоставляю явный trackBy
-Function, это означает, что angular должен отслеживать по идентификатору, который (в случае выше) абсолютно правильно идентифицирует каждый объект и соответствует тому, что ожидает документация.
В ответе Морфиша в основном говорится, что функция отслеживания по идентичности не работает, и предлагается использовать id
-Property. Сначала это казалось решением, но потом оказалось просто ошибкой. Использование идентификатора -Property демонстрирует то же поведение, что и мой тест выше.
Ответ от penleychan отслеживает по индексу, что заставляет angular думать, что после того, как я переместил значение, angular думает, что на самом деле я нажал значение, и все значения в массиве только что обновились. Это вроде работает вокруг проблемы, но это нарушает контракт Track-By, и это наносит ущерб цели отслеживания -Function (чтобы уменьшить отток в DOM).