我在Angular应用中遇到onPush更改检测问题。
我创建了一个演示应用程序来说明问题: https://stackblitz.com/edit/angular-vcebqu
该应用程序包含一个parent
组件和一个child
组件。
parent
和child
都使用onPush更改检测。
parent
和child
的输入都分为获取器和设置器,其中this.cd.markForCheck();
用于设置器。
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
组件使用child
循环创建多个*ngFor
组件,如下所示:
<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更改检测,它将正常工作。
答案 0 :(得分:1)
由于传递给子组件的输入不是数组,因此IterableDiffers将不起作用。 KeyValueDiffers在这种情况下可以用来监视输入对象中的更改,然后相应地进行处理(stackblitz link):
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 };
}
}
}
答案 1 :(得分:1)
只需添加替代解决方案,以防其他人遇到此问题。 ChildComponent不能在其模板中反映新值的主要原因是因为父组件仅更改了“ element”的属性“ order”,因此父组件将相同的对象引用注入经过修改的“ order”属性。
仅当将新的对象引用注入到组件中时,OnPush更改检测策略才会“检测更改”。因此,为了让ChildComponent(具有OnPush更改检测策略)触发更改检测,您必须向“ element”输入属性注入新的对象引用,而不是相同的对象。
要查看实际效果,请打开https://stackblitz.com/edit/angular-vcebqu并进行更改。
在文件
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属性除外。
答案 2 :(得分:0)
您必须将<comment v-for="comment in comments" :key="comment.id" :data="comment" />
装饰器添加到 setter 方法。
@Input()
还有其他一些事情:
get element() {
return this._element;
}
@Input()
set element(newVal: any) {
this._element = newVal;
}
仅在输入更改时设置输入。OnPush
,因为该组件已经脏了。