Comparing the use of the index and item id in Angular's trackBy for ngFor loop

My ngFor loop is extensive and changes frequently due to asynchronous data. Implementing TrackBy has made a noticeable difference in only refreshing the changing part, improving performance significantly. I understand the benefits of having a trackBy function return a unique ID, but I have come across samples that simply return the current index instead.

public trackByFn(index, item) { return index }

In my table, I have not observed any differences between returning 'index' or 'item.id'. Both options seem to optimize the rendering process effectively (although there may be some edge cases I haven't encountered).

Can someone please clarify what exactly happens when I return the index in the trackBy function?

Answer №1

After reading your input and feeling curious, I delved deep into the angular differ code. I have dissected the behavior in three different scenarios for you and believe it's valuable knowledge to possess:

First scenario:

If no trackBy is defined:

<div *ngFor="let obj of arrayOfObj">{{obj.key}}</div>

In this case, when a trackBy function isn't specified, Angular loops through the array, creates the DOM elements, and binds the data within the template using [ngForOf]. (The above code can be rendered as):

<ng-template ngFor let-obj [ngForOf]="arrayOfObj">
  <div>{{obj.key}}</div>
</ng-template>

Initially, it generates all those div elements which is consistent across all three possibilities. However, when new data arrives from the API with similar but not identical objects, Angular compares them based on identity using ===. This method works well for strings, numbers, and other primitives, but falls short for objects. Subsequently, when Angular checks for changes, it cannot locate the original objects in order to update them. As a result, the DOM elements are removed and rebuilt entirely.

Even if the data remains unchanged, different identities of objects in the second response prompt Angular to recreate the entire DOM structure (as if old elements were deleted and new ones inserted).

This process can be resource-intensive both in terms of CPU usage and memory consumption.

Second scenario:

trackBy defined with object key:

<div *ngFor="let obj of arrayOfObj;trackBy:trackByKey">{{obj.key}}</div>

trackByKey = (index: number, obj: object): string => {
  return object.key;
};

This approach is the most efficient one. When new data containing objects with different identities than before is received, Angular utilizes the trackBy function to determine the object's identity. It then matches it against existing (and previously deleted if not found) DOM elements. If a match is found, it updates the bindings inside the template. Otherwise, it searches through the removed objects and creates a new DOM element if necessary. Thus, this process is swift as it involves updating existing DOM elements and their bindings.

Third scenario:

trackBy defined with array index

<div *ngFor="let obj of arrayOfObj;trackBy:trackByIndex">{{obj.key}}</div>

trackByIndex = (index: number): number => {
  return index;
};

Similar to tracking by object key, this method keeps updating the bindings within templates even when items are repositioned within the array. While still efficient, it may not be the quickest method available, although it certainly beats reconstructing the entire DOM structure.

Hopefully, you now grasp the distinctions between these approaches. As an added tip, if your business objects share a common way to access their identity (such as a property like .id or .key), you can extend the native *ngFor and create a custom structural directive with a built-in trackBy function. Here's a snippet that has not been tested:

export interface BaseBo {
  key: string;
}

@Directive({selector: '[boFor][boForOf]'})
export class ForOfDirective<T extends BaseBo> extends NgForOf<T> {
  @Input()
  set boForOf(boForOf: T[]) {
    this.ngForOf = boForOf;
  }

  ngForTrackBy = (index: number, obj: T) => obj.key;
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error: The property 'attach' of undefined is not readable in makeStyles.js

Encountering a series of type errors when attempting to access the Login component of my MERN app on the production version, as shown in this image: https://i.sstatic.net/6apXu.png The app (https://github.com/ahaq0/kumon_schedule) functions perfectly on ...

Angular mistakenly uses the incorrect router-outlet

Encountering an issue with Angular routing. The main app has its own routing module, and there is a sub module with its own routing module and router-outlet. However, the routes defined in the submodule are being displayed using the root router outlet inst ...

Removing invalid characters in a *ngFor loop eliminates any elements that do not meet the criteria

I am facing an issue with my *ngFor loop that is supposed to display a list of titles. fetchData = [{"title":"woman%20.gif"},{"title":"aman",},{"title":"jessica",},{"title":"rosh&quo ...

Transmit information using JSON format in Angular 8 using FormData

i am struggling with sending data to the server in a specific format: { "name":"kianoush", "userName":"kia9372", "email":"<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="bcd7d5ddd8ce85...@example.com</a>" } H ...

Can an object type be partially defined?

Here is a similar scenario: function(param1, { knownParam1, ...opts }) To better describe param1 and knownParam1, perhaps something like this could work: type Param2 = { knownParam1: string, ...otherParams: any } type Parameters = { param1: str ...

Ways to retrieve and upload a blob from a file object

After receiving a file object via an HTML input on-change event in my component.html: onFileSelected(event) { this.selectedFile = event.target.files[0]; } Now, I need to send a POST request to upload the image to the database. The database only acc ...

Escape from the abyss of callback hell by leveraging the power of Angular, HttpClient, and

I'm currently grappling with understanding Angular (2+), the HttpClient, and Observables. I'm familiar with promises and async/await, and I'm trying to achieve a similar functionality in Angular. //(...) Here's some example code showca ...

Error encountered when attempting to export a TypeScript class from an AngularJS module

In my application using Angular and TypeScript, I have encountered a scenario where I want to inherit a class from one module into another file: generics.ts: module app.generics{ export class BaseClass{ someMethod(): void{ alert(" ...

Guide on triggering background notifications to display in the foreground of an Angular Firebase application using Angular 9 webapp

I've been using Angular Firebase for push notifications and everything is going smoothly with foreground messages. However, I encountered an issue when trying to handle background messages by adding a 'setBackgroundMessageHandler' call in my ...

How can I trigger a service method in Angular 2 whenever an observable changes or when the service provides new data?

In my app component, I have implemented a search input and an observable called resultItems: Observable<Array<string>>;. This observable is powered by a search service that returns results to the UI using *ngFor. Additionally, there is a leafle ...

Encountered difficulties when attempting to install Angular Universal in my angular13 application

I'm facing some challenges while trying to incorporate @nguniversal/ into my angular 13 application. Are there any experts who can offer assistance? Angular CLI: 13.0.4 Node: 16.13.1 Package Manager: npm 8.1.2 npm ERR! code ERESOLVE npm ERR! ERESO ...

Customize the CSS property in a scss Page

Currently, I am diving into the world of ionic. Within my app, I have a linkbutton where I am attempting to set the font size to 12px. However, it seems that this style is being overridden by other CSS properties. The SCSS file associated with the linkbutt ...

Invoke the API when the value of a property in the SPFX property pane is modified

Here's a question that might sound silly, but I'll ask anyway. I have a dropdown field in my Property pane that is filled with all the lists from the current site. It's working fine. When I change the dropdown selection, it populates a pro ...

How can I utilize identical cucumber steps for both mobile and web tests while evaluating the same functionality?

In order to test our website and React Native mobile app, we have developed a hybrid framework using webdriver.io and cucumber.io. We currently maintain separate feature files for the same functionality on both the web and mobile platforms. For example, i ...

Simulating NgRx store using a selector in Angular version 10

I am a novice when it comes to creating test cases, however, I have been tasked with writing tests for an Angular 10 app that utilizes ngrx to manage user information. Below is the ngOnInit function: this.store.pipe(select(userInfo)).subscribe((data) => ...

Is it possible to subscribe multiple times in Angular without triggering multiple requests?

I have a service call in Angular that caches its response like so: public cacheMyServiceResponse(): Observable<any> { return this.appConfig.getEndpoint('myService') .pipe( switchMap((endpoint: Endpoint) => this.http.get(endp ...

Tips for parsing text responses in React to generate hyperlinks and emphasize specific words

I'm currently tackling a React project and facing an interesting challenge. I have a text response that needs to be parsed in a way that all URLs are automatically turned into clickable hyperlinks (using anchor tags). Moreover, there's a requirem ...

Resolving the Error: "Type 'Customer | undefined' is not compatible with type 'Customer'" in Angular

I encountered an issue with the following code: ... export class ListCustomersComponent implements OnInit { customers: Array<Customer> = []; showCustomer?: Customer; isSelected: boolean = false; deletedCustomer?: Customer; returnedMessa ...

Typescript and Apollo Client return types intertwined

My goal is to create a simple function within a class that generates an Apollo Client. Below is the code I have implemented: import appConfig from 'config/app-config'; import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/clie ...

Determine the location of the vertical scroll bar on the p-table

I am currently using a PrimeNG p-table within my Angular application. <p-table #tab [value]="logs" [resizableColumns]="true" columnResizeMode="expand" [tableStyle]="{'min-width': '50rem'}" [col ...