[英]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.