簡體   English   中英

Angular OnPush 更改檢測傳播到 ngFor 循環中的子組件

[英]Angular OnPush Change Detection Propagation to a Child Components in a ngFor Loop

我在 Angular 應用程序中遇到 onPush 更改檢測問題。

我創建了一個演示應用程序來說明問題: https : //stackblitz.com/edit/angular-vcebqu

該應用程序包含一個parent組件和一個child組件。

parentchild都在使用 onPush 更改檢測。

parentchild都將輸入分解為 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;
}

這里還有一些其他的東西:

  • Angular 不會設置重復值,因為OnPush僅在輸入更改時設置輸入。
  • 不要在setter 中調用this.cd.markForCheck() ,因為組件已經臟了。
  • 您不必從輸入中輸出值。 父組件已經知道輸入了什么。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM