gpt4 book ai didi

javascript - ngFor + ngModel : How can I unshift values to the array I am iterating?

转载 作者:可可西里 更新时间:2023-11-01 01:42:17 25 4
gpt4 key购买 nike

我有一个元素数组,用户不仅可以编辑,还可以添加和删除完整的数组元素。这很好用,除非我尝试将一个值添加到数组的开头(例如使用 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 属性,他们也只是附加,而不是前置)。

为什么会发生这种情况,我该怎么办?

关于trackBy的注释

到目前为止,答案只是“使用 trackBy”。但是 trackBy 的文档指出:

By default, the change detector assumes that the object instance identifies the node in the iterable

由于我没有提供显式的 trackBy-Function,这意味着 angular 应该通过身份进行跟踪,(在上面的例子中)绝对正确地识别每个对象并与什么内联文档期望。

Morphyish 的回答基本上指出按身份跟踪的功能已损坏,并建议使用 id-Property。起初它似乎是一个解决方案,但后来证明它只是一个错误。使用 id-Property 表现出与我上面的测试完全相同的行为。

penleychan 的回答按索引跟踪,这导致 angular 认为,在我取消移动一个值后,angular 认为实际上我推了一个值,而数组中的所有值恰好都更新了。它在某种程度上解决了这个问题,但它违反了 track-By 契约,并且违背了 track-by-function 的目的(减少 DOM 中的流失)。

最佳答案

编辑

经过进一步调查,问题实际上并非来自 ngFor。它是使用输入的 name 属性的 ngModel

在循环中,name 属性是使用数组索引 生成的。然而,当在数组的开头放置一个新元素时,我们突然有了一个同名的新元素。

这可能会与内部观察相同输入的多个 ngModel 产生冲突。

在数组开头添加多个输入时,可以进一步观察到此行为。最初使用相同的 name 属性创建的所有输入都将采用正在创建的新输入的值。不管它们各自的值是否改变。

要解决此问题,您只需为每个输入指定一个唯一的名称。要么使用唯一的 id,如下面的示例

<input [name]="'elem' + item.id" [(ngModel)]="item.value">

或者通过使用唯一的名称/ID 生成器(类似于 Angular Material 所做的)。


原始答案

如 penleychan 所述,问题是 ngFor 指令中缺少 trackBy

您可以找到您正在寻找的工作示例 here

使用您示例中的更新代码

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; trackBy: trackByFn">
<input [name]="'elem' + index" [(ngModel)]="item.value">
</div>
</form>`
})
class TestComponent {
values: {id: number, value: string}[] = [{id: 0, value: 'a'}, {id: 1, value: 'b'}];
trackByFn = (index, item) => item.id;
}

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({id: 2, 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({id: 2, value: 'z'});
fixture.detectChanges();
await fixture.whenStable();

// evaluation
console.log(JSON.stringify(getAllValues())); // Logs '["z","z","b"]'
expect(getAllValues()).toEqual(['z', 'a', 'b']);
});
});

尽管有您的评论,但它不是解决方法。 trackBy 是针对使用类型(以及性能,但两者是相关联的)而设计的。

你可以找到ngForOf指令代码here如果您想亲自看看,但这是它的工作原理。

ngForOf 指令对数组进行比较以确定所做的修改,但是如果没有传递特定的 trackBy 函数,它就会进行软比较。这适用于简单的数据结构,例如字符串或数字。但是当您使用 Objects 时,它会变得非常快。

除了降低性能之外,数组中的项目缺乏明确的标识会迫使数组重新呈现整个元素。

但是,如果ngForOf指令能够清楚地判断出哪一项发生了变化,哪一项被删除了,哪一项被添加了。它可以保持所有其他项目不变,根据需要从 DOM 添加或删除模板,并仅更新需要的项目。

如果添加 trackBy 函数并在数组的开头添加一个项目,diffing 可以意识到这正是发生的情况,并在循环的开头添加一个新模板 while将相应的项目绑定(bind)到它。

关于javascript - ngFor + ngModel : How can I unshift values to the array I am iterating?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57163131/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com