简体   繁体   中英

Angular efficiently using trackBy with ngFor

In Angular, is the trackBy function necessary for *ngFor? I saw a few articles here , here , here , and here that say using trackBy will improve performance and has better memory management. But I was wondering if trackBy is such an improvement, then why isn't it default behavior? Is it default behavior and everything I am looking at is out of date?

If it isn't default behavior, my project has about 90 *ngFor in 90 components and I was wondering if there was a way to use the trackBy where I am not including the following function 90 times. I also want to avoid adding a service and importing that 90 times.

HTML

<mat-option *ngFor="let l of list; trackBy: trackByFn" [value]="l.id">
   {{l.name}}
</mat-option>

TS

trackByFn(index, item) {
    return index
}

notice that none of your examples use the index (besides one unreliable medium article), they all use a unique identifier of the object that angular can't possibly know unless you tell it.

Returning index alone has a use case but it's a fairly uncommon one. It's basically telling angular to never rerender the existing items in this list since the index of a given item will never change. This usually is a very unexpected behavior for developers since the initialization lifecycle hooks of sub components won't reexecute. This is generally safe to do though in ngFor's that don't have sub components but these kinds of lists are generally more performant anyway and you won't see much benefit unless the lists are very long or change frequently.

The idea of trackBy is to allow you to reinitialize items in lists that need it and not reinitialize ones that don't. It isn't a silver bullet for blindly increasing performance like some people treat it, it's purpose and functionality should be fully understood. Keep in mind that just because an item has a unique ID doesn't mean it is appropriate to use in a trackBy function. trackBy is meant to tell angular when an item needs to be re-rendered, ie when I need those life cycle hooks to re run. If the ID stays the same but the contents can change, depending on how you've built a certain component, that component might need to be reinitialized anyway.

An optimized solution:

Problem: Rendering views is an expensive task of O(n) list


Solution: we are typically rolling the viewport on scrolls which involves removing and adding with the same number of views. rather than deleting the views and creating them all again, we recycle them. So we detach the views, remove the context, and cache them so we can attach them and re-context them on the add cycle. Therefore saving a considerable number of script/render cycles. (In android it's called recycler view)

here is are few angular library that does that exactly as mentioned, better performance than trackBy function

  1. https://material.angular.io/cdk/scrolling/overview

  2. https://github.com/rintoj/ngx-virtual-scroller(this lib is no longer maintained and does not work with latest angular versions but if you still wanted to use copy-paste to your source and import in your module)

  3. https://github.com/anagram4wander/ng-vfor-lib

Give it a class for test

export class Item {
  id: number;
  name: string;
}

And add a directive to monitor its init and destory

@Directive({selector: '[appMonitor]'})
export class MonitorDirective implements OnInit, OnDestroy {


  ngOnInit(): void {
    console.log('init');
  }

  ngOnDestroy(): void {
    console.log('destroy');
  }
}

Init an arrary

  itemArray: Item[] = [
    {id: 1, name: 'Tom'},
    {id: 2, name: 'Joe'},
    {id: 3, name: 'KK'}
  ];

Two functions to change the array content

  allFoo(): void {
    this.itemArray = [
      {id: 1, name: 'Tom_foo'},
      {id: 2, name: 'Joe_foo'},
      {id: 3, name: 'KK_foo'}
    ];
  }

  allBar(): void {
    this.itemArray = [
      {id: 1, name: 'Tom_bar'},
      {id: 2, name: 'Joe_bar'},
      {id: 3, name: 'KK_bar'}
    ];
  }

The preparation is done,so far so good. First of all let's test without trackBy

  <div *ngFor="let item of itemArray " appMonitor>
    Id: {{item.id}} Name:{{item.name}}
  </div>

在此处输入图片说明

Clearlly every time you change the array angular has recreate component accordingly.Let's try trackBy this time:

<div *ngFor="let item of itemArray ;trackBy:identify" appMonitor>
  Id: {{item.id}} Name:{{item.name}}
</div>

Identity:

  identify(index: number, item: Item): number {
    return item.id;
  }

在此处输入图片说明

The component is being resued.So we can conclude that using trackBy can save the work from creating same component in hmtl.

I have created an animation which shows how both ngFor and ngFor with trackBy manipulates DOM side by side. 在此处输入图像描述

read article here: https://link.medium.com/ckBRk9wrinb

这是一个测试,看看我是否会得到徽章

I will delete this soon after posting

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM