- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
我有一个元素数组,用户不仅可以编辑,还可以添加和删除完整的数组元素。这很好用,除非我尝试将一个值添加到数组的开头(例如使用 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/
在 C 中: int a[10]; printf("%p\n", a); printf("%p\n", &a[0]); 产量: 0x7fff5606c600 0x7fff5606c600 这是我所期望
我一直在尝试运行此循环来更改基于数组的元素的位置,但出现以下错误。不太确定哪里出了问题。任何想法或想法!谢谢。 var population = [[98, 8, 45, 34, 56], [9, 1
我正在尝试获取一个 Ruby 数组数组并将其分组以计算其值。 数组有一个月份和一个 bool 值: array = [["June", false], ["June", false], ["June"
所以我们的目标是在遇到某个元素时将数组分割成子数组下面的示例 array.split("stop here") ["haii", "keep", "these in the same array bu
在this问题已经回答了两个表达式是相等的,但在这种情况下它们会产生不同的结果。对于给定的 int[] 分数,为什么会这样: Arrays.stream(scores) .forEac
我认为我需要的是哈希数组的数组,但我不知道如何制作它。 Perl 能做到吗? 如果是这样,代码会是什么样子? 最佳答案 perldoc perldsc是了解 Perl 数据结构的好文档。 关于arra
我遇到了这个问题,从 API 中我得到一个扩展 JSON,其中包含一个名为坐标的对象,该对象是一个包含数组 o 数组的数组。 为了更清楚地看这个例子: "coordinates": [
postgres 中有(v 9.5,如果重要的话): create table json_test( id varchar NOT NULL, data jsonb NOT NULL, PRIM
我用 echo "${array[@]}" 和 echo "${array[*]}" 得到了相同的结果。 如果我这样做: mkdir 假音乐; touch fakemusic/{Beatles,Sto
我正在尝试创建 typealias 对象的数组数组 - 但我收到“表达式类型不明确,没有更多上下文”编译错误。这是我的代码: typealias TestClosure = ((message: St
如果您在 Python 中创建一维数组,使用 NumPy 包有什么好处吗? 最佳答案 这完全取决于您打算如何处理数组。如果您所做的只是创建简单数据类型的数组并进行 I/O,array模块就可以了。 另
当我将数组推送到只有一个数组作为其唯一元素的数组数组时,为什么会得到这种数据结构? use v6; my @d = ( [ 1 .. 3 ] ); @d.push( [ 4 .. 6 ] ); @d.
在 Julia 中,我想将定义为二维数组向量的数据转换为二维矩阵数组。 如下例所述,我想把数据s转换成数据t,但是至今没有成功。 我该如何处理这个案子? julia> s = [[1 2 3], [4
C 没有elementsof 关键字来获取数组的元素数。所以这通常由计算 sizeof(Array)/sizeof(Array[0]) 代替但这需要重复数组变量名。1[&Array] 是指向数组后第一
所以,假设我有一个像这样的(愚蠢的)函数: function doSomething(input: number|string): boolean { if (input === 42 || in
我有以下数组: a = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] 我将它用于一些像这样的视觉内容: 1 2 3 4 5 6 7 8 9 10
我想知道数组中的 .toList 与 .to[List] 之间有什么区别。我在spark-shell中做了这个测试,结果没有区别,但我不知道用什么更好。任何意见? scala> val l = Arr
我很难获得完全相同对象的多个元素的当前元素索引: $b = "A","D","B","D","C","E","D","F" $b | ? { $_ -contains "D" } 替代版本: $b =
我正在尝试使用来自我的 API 的 v-select 执行 options,我将数据放在数组数组中。 Array which I got from API 它应该是一个带有搜索的 select,因为它
这个问题在这里已经有了答案: String literals: pointer vs. char array (1 个回答) 4 个月前关闭。 当我执行下一个代码时 int main() {
我是一名优秀的程序员,十分优秀!