繁体   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