[英]Angular OnPush Change Detection Propagation to a Child Components in a ngFor Loop
我在 Angular 应用程序中遇到 onPush 更改检测问题。
我创建了一个演示应用程序来说明问题: https : //stackblitz.com/edit/angular-vcebqu
该应用程序包含一个parent
组件和一个child
组件。
parent
和child
都在使用 onPush 更改检测。
parent
和child
都将输入分解为 getter 和 setter,使用this.cd.markForCheck();
在 setter 中使用。
private _element: any;
@Output()
elementChange = new EventEmitter<any>();
@Input()
get element() {
return this._element;
}
set element(newVal: any) {
if (this._element === newVal) { return; }
this._element = newVal;
this.cd.markForCheck();
this.elementChange.emit(this._element);
}
parent
组件使用*ngFor
循环创建多个child
组件,如下所示:
<app-child
*ngFor="let element of item.elements; let index = index; trackBy: trackElementBy"
[element]="item.elements[index]"
(elementChange)="item.elements[index]=$event"></app-child>
问题是,如果数据在parent
组件中更新,则更改不会向下传播到child
组件。
在演示应用程序中,单击“更改”按钮并注意“元素”数组中的第一个“元素”( elements[0].order
)在父级中更新,但更改未显示在第一child
组件的“元素”。 但是,如果从child
组件中删除 OnPush 更改检测,则它可以正常工作。
只需添加替代解决方案,以防其他人遇到此问题。 ChildComponent 没有在其模板中反映新值的主要原因是因为只有 'element' 的属性 'order' 是从父组件更改的,因此父组件正在注入具有修改过的 'order' 的相同对象引用财产。
OnPush 更改检测策略只会在新的对象引用注入组件时“检测更改”。 因此,为了让 ChildComponent(具有 OnPush 更改检测策略)触发更改检测,您必须向“元素”输入属性注入一个新的对象引用而不是相同的对象引用。
要查看此操作,请打开https://stackblitz.com/edit/angular-vcebqu并进行 ff 更改。
在文件
parent.component.ts
,将方法onClick($event) {...}
为:
onClick(event){
const random = Math.floor(Math.random() * (10 - 1 + 1)) + 1;
this.item.elements[0] = {...this.item.elements[0], order: random};
}
最后一行将数组中索引 0 处的对象引用替换为与数组中旧的第一个元素相同的新对象,但 order 属性除外。
由于传入子组件的输入不是数组,因此 IterableDiffers 将不起作用。 然而,在这种情况下可以使用KeyValueDiffers来观察输入对象的变化,然后相应地处理它( stackblitz 链接):
import {
Component,
OnInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
KeyValueDiffers,
KeyValueDiffer,
EventEmitter,
Output, Input
} from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
private _element: any;
@Output()
elementChange = new EventEmitter<any>();
get element() {
return this._element;
}
@Input()
set element(newVal: any) {
if (this._element === newVal) { return; }
this._element = newVal;
this.cd.markForCheck();
this.elementChange.emit(this._element);
}
private elementDiffer: KeyValueDiffer<string, any>;
constructor(
private cd: ChangeDetectorRef,
private differs: KeyValueDiffers
) {
this.elementDiffer = differs.find({}).create();
}
ngOnInit() {
}
ngOnChanges() {
// or here
}
ngDoCheck() {
const changes = this.elementDiffer.diff(this.element);
if (changes) {
this.element = { ...this.element };
}
}
}
您必须将@Input()
装饰器添加到setter方法中。
get element() {
return this._element;
}
@Input()
set element(newVal: any) {
this._element = newVal;
}
这里还有一些其他的东西:
OnPush
仅在输入更改时设置输入。this.cd.markForCheck()
,因为组件已经脏了。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.