Angular performance: change detection detached if component is out of viewport

别等时光非礼了梦想. 提交于 2020-01-15 03:22:08

问题


I want detach the change detection for all the compontents out of the current viewport

see demo online

import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef, ElementRef, ViewChild, OnInit, OnDestroy, AfterViewInit } from '@angular/core';

@Component({
  selector: 'hello',
  template: `<div #counter>[{{index}}] {{count}}</div>`,
  styles: [`div { border: 1px solid red; padding: 5px 5px 5px 5px; }`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HelloComponent implements OnInit, AfterViewInit {

  @ViewChild('counter', { static: false }) counter: ElementRef;
  @Input() index: number;

  public count = 0;
  public visible = true;

  constructor(private cdr: ChangeDetectorRef){}

  ngOnInit() {
    setInterval(() => {
      this.count++;
      this.cdr.markForCheck();
    }, 1000);
  }

  ngAfterViewInit() {
      const hideWhenBoxInView = new IntersectionObserver((entries) => {
        if (entries[0].intersectionRatio <= 0) { // If not in view
          this.cdr.detach();
          this.visible = false;
        } else {
          this.visible = true;
          this.cdr.reattach();
          this.cdr.markForCheck();
        }
        // console.log(this.index, this.visible);
      });
      hideWhenBoxInView.observe(this.counter.nativeElement);
  }
}

it works, but with over 1000 components the performance is very bad.

Are my attaching/detaching change detection correct?


回答1:


You are calling setInterval() for every component including those that are not in view. Change detection is not running but you are still calling the function in setInterval() 1000 times per second which explains the lag.

By the way, rendering a scroll list with 1000 items affects performance too. Browsers will render everything and need to calculate various paints when scrolling through the list despite being out of viewport. You should render such long list lazily, see Virtual Scrolling in Angular 7

You are also calling .markForCheck() on components that are out of view, check whether component is visible before calling that.

See StackBlitz

ngOnInit() {
  this.subscriptions.add(
    interval(1000).subscribe(() => {
      this.count++;
      if (this.visible) {
        this.cdr.markForCheck();
      }
    })
  );
}

ngOnDestroy() {
  this.subscriptions.unsubscribe();
}

ngAfterViewInit() {
  const hideWhenBoxInView = new IntersectionObserver(entries => {
    if (entries[0].intersectionRatio <= 0) {
      // If not in view
      this.cdr.detach();
      this.visible = false;
    } else {
      this.visible = true;
      this.cdr.reattach();
      this.cdr.markForCheck();
    }
  });
  hideWhenBoxInView.observe(this.counter.nativeElement);
}




回答2:


Maybe using trackBywill allow to avoid check whether it is in viewport.

<li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>

trackBy gives you a possibility to choose what property/condition angular should check changes against.

trackByFn(index, item) {
  return item.someUniqueIdentifier;
  // return index(if id is not unique) or unique id;
}

As Angular docs says:

A function that defines how to track changes for items in the iterable.

When items are added, moved, or removed in the iterable, the directive must re-render the appropriate DOM nodes. To minimize churn in the DOM, only nodes that have changed are re-rendered.

By default, the change detector assumes that the object instance identifies the node in the iterable. When this function is supplied, the directive uses the result of calling this function to identify the item node, rather than the identity of the object itself.

The function receives two inputs, the iteration index and the node object ID.



来源:https://stackoverflow.com/questions/59248167/angular-performance-change-detection-detached-if-component-is-out-of-viewport

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!