我有以下令人困惑的 Angular 问题。
编辑:我设法在 Stackblitz 示例中重现了这一点,并在此处更新了文本。
Stackblitz 示例 here .
这显示了四组带有删除图标的三个列表条目,具有以下配对:
string input/passed from a getter
string input/passed from a field
array input/passed from a getter
array input/passed from a field
在数组/getter 的情况下,当我点击第一个(来自items1
)时,没有任何内容被记录到控制台;但是单击第二个(来自 items2
)或第三个(来自 items3
),它会记录 'delete'
。在其他情况下,它总是记录删除,如预期的那样。
这里可能发生了什么?
代码如下,尽管 Stackblitz很容易玩。
首先是父级HTML,它设置了四种情况:
String with getter
<hello [name]="name1">
</hello>
<hr/>
String without getter
<hello [name]="name2">
</hello>
<hr/>
Array with getter
<hello [names]="names1">
</hello>
<hr/>
Array without getter
<hello [names]="names2">
</hello>
和 typescript :
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
get name1(): string {
return 'Angular';
}
name2 = 'Angular';
get names1(): string[] {
return ['Angular'];
}
names2 = ['Angular'];
}
然后是组件 HTML:
<mat-list>
<ng-container *ngFor="let item of items1">
<mat-list-item>
<h2 mat-line>Some Text</h2>
<button mat-icon-button (click)="delete()"><mat-icon>delete</mat-icon></button>
</mat-list-item>
</ng-container>
<ng-container *ngFor="let item of items2">
<mat-list-item>
<h2 mat-line>Some Text</h2>
<button mat-icon-button (click)="delete()"><mat-icon>delete</mat-icon></button>
</mat-list-item>
</ng-container>
<ng-container *ngFor="let item of items3">
<mat-list-item>
<h2 mat-line>Some Text</h2>
<button mat-icon-button (click)="delete()"><mat-icon>delete</mat-icon></button>
</mat-list-item>
</ng-container>
</mat-list>
和 typescript :
@Component({
selector: 'hello',
templateUrl: './hello.component.html',
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
@Input() name: string;
@Input() names: string[];
items1: Number[] = [];
items2: Number[] = [];
items3 = [new Number(42)];
ngOnChanges() {
this.items1 = [new Number(42)];
this.items2 = [42];
}
delete() {
console.log('delete');
}
}
最佳答案
这里的一个问题(尽管不是根本原因)是当使用 ngFor
循环时,Angular 使用对象标识来跟踪它是否需要重新渲染。查看docs供讨论。因此(见下文)添加一个 trackBy
函数解决了这个问题。
这里的坏情况是:
- 将 getter 用于列表的输入会导致
ngOnChanges
被调用两次,并具有相同的值。大概 Angular 访问输入两次(我不知道为什么),但是当它访问时,getter 每次都返回一个新列表,它具有不同的标识,因此ngOnChanges
被调用两次。< - 不会使用
string
getter 调用两次;string
对象是相同的。 - 调用
ngOnChanges
时,它会重置错误列表 (items1
) 以包含新对象。
结果是,在这种情况下,Angular 渲染列表两次,因为它认为所有元素从一次传递到下一次都发生了变化。
现在:我不知道为什么这会导致按钮不可点击。但是 Angular 允许您覆盖 trackBy
函数,它用于确定列表的元素是否已更改。并将其更改为按索引进行跟踪(此处未更改)可以解决此问题。
fork Stackblitz example解决问题,它补充说:
到组件的 Typescript:
trackByIndex(index, item) {
return index;
}
在 HTML 循环中:
<ng-container *ngFor="let item of items1; trackBy: trackByIndex">
<mat-list-item>
<h2 mat-line>Some Text</h2>
<button mat-icon-button (click)="delete()"><mat-icon>delete</mat-icon></button>
</mat-list-item>
</ng-container>
关于当从 Typescript getter 初始化输入数组时迭代新对象时, Angular Material 按钮不可单击,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56203596/