ngFor循环中对子组件的角度OnPush变化检测传播

时间:2019-02-07 01:57:11

标签: javascript angular typescript angular2-changedetection

我在Angular应用中遇到onPush更改检测问题。

我创建了一个演示应用程序来说明问题: https://stackblitz.com/edit/angular-vcebqu

该应用程序包含一个parent组件和一个child组件。

parentchild都使用onPush更改检测。

parentchild的输入都分为获取器和设置器,其中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更改检测,它将正常工作。

3 个答案:

答案 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()

还有其他一些事情:

  • Angular不会设置重复的值,因为get element() { return this._element; } @Input() set element(newVal: any) { this._element = newVal; } 仅在输入更改时设置输入。
  • 请勿在设置程序中调用OnPush,因为该组件已经脏了。
  • 您不必从输入中输出值。父组件已经知道输入了什么。