Using a method call instead of a property for the ngFor directive can lead to an infinite loop of loading

Within my template, I have included the following code:

<a *ngFor="let item of navItems"
   [routerLink]="item.link"
   routerLinkActive="active"
   class="navigation-item"
   [ngClass]="{'enabled': item.enabled}"
>
  <span class="color-{{item.color}}">
    <mat-icon>{{item.icon}}</mat-icon>
  </span>
  <div class="label">{{item.label}}</div>
</a>

The array navItems is defined within my component as follows:

navItems: NavItem[] = [
  {
    link: 'link1', label: 'Label 1', icon: 'thumb_up', color: 'green', enabled: true
  },
  {
    link: 'link2', label: 'Label 2', icon: 'thumb_down', color: 'red', enabled: true
  },
  {
    link: 'link3', label: 'Label 3', icon: 'insert_drive_file', color: 'blue', enabled: true
  },
  {
    link: 'link4', label: 'Label 4', icon: 'note_add', color: 'blue', enabled: true
  },
];

Although this setup works properly, I now require the ability for the contents of navItems to dynamically change. I attempted converting the navItems property into a getter like so:

get navItems(): NavItem[] {
  return [
    {
      link: 'link1', label: 'Label 1', icon: 'thumb_up', color: 'green', enabled: true
    },
    {
      link: 'link2', label: 'Label 2', icon: 'thumb_down', color: 'red', enabled: true
    },
    {
      link: 'link3', label: 'Label 3', icon: 'insert_drive_file', color: 'blue', enabled: true
    },
    {
      link: 'link4', label: 'Label 4', icon: 'note_add', color: 'blue', enabled: true
    },
  ];
}

Unfortunately, when attempting this modification, the browser tab enters an infinite loading loop upon loading the component, requiring termination via the task manager - no console output is generated.

I also experimented with using a regular method call instead of a getter to supply the array, but faced the same issue.

The array being returned consists solely of plain objects with directly assigned string and boolean values, ruling out any nested calls that could trigger recursion.

Is there an error in my approach? Can method calls / getters not be used to provide data for the ngFor directive?

Answer №1

Upon utilizing the trackBy function, the issue was successfully resolved.

Within the HTML file:

<a *ngFor="let item of navItems; trackBy: trackByFn" [routerLink]="item.link" routerLinkActive="active"
class="navigation-item"
[ngClass]="{'enabled': item.enabled}"
>
<span class="color-{{item.color}}">
 <mat-icon>{{item.icon}}</mat-icon>
</span>
<div class="label">{{item.label}}</div>
</a>

Within the TS file:

export class AppComponent {
   trackByFn(index,item){
     console.log(index,"n:",item);
     return index;
   }
  get navItems(): NavItem[] {
    return [
      {
        link: 'link1', label: 'Label 1', icon: 'thumb_up', color: 'green', enabled: true
      },
      {
        link: 'link2', label: 'Label 2', icon: 'thumb_down', color: 'red', enabled: true
      },
      {
        link: 'link3', label: 'Label 3', icon: 'insert_drive_file', color: 'blue', enabled: true
      },
      {
        link: 'link4', label: 'Label 4', icon: 'note_add', color: 'blue', enabled: true
      },
    ];
  }
}
class NavItem {
  link;
   label;
    icon;
     color;
     enabled;
}

Note:

Case-1 : The trackBy function works fine without the routerLinkActive directive.

Case-2 : The trackBy function works fine with the routerLinkActive directive.

Case-3 : With routerLinkActive but without trackBy, it results in a loop.

The continuous loops are caused by the update() method within the RouterLinkActive directive, which executes hooks like AfterContentInit. Commenting out update() or using trackBy resolves this issue.

Each time the navItems method is called, the number of elements created corresponds to the items in the array. Directives apply hooks such as AfterContentInit and OnDestroy to manipulate elements. Without trackBy, elements are constantly recreated, resulting in a loop.

In summary, without trackBy, each method return creates new elements which are then manipulated by directives. This repetitive process leads to an infinite loop.

For further information, refer to:

Additionally, consider creating a CustomRouterLinkActive for debugging purposes using the source from https://github.com/angular/angular

Answer №2

It appears that the issue at hand arises from having the array stored in an instance variable, causing it to remain constant. Each time Angular checks for changes, it sees the same array and doesn't take further action.

However, by converting the instance variable into a getter method, a new array is generated each time navItems is accessed. Consequently, Angular detects a different value during change detection, even if the items themselves are conceptually identical. In essence:

// Previously:
const a = comp.navItems;
const b = comp.navItems;
console.log(a === b); // true. Both point to the same object.

But now:

const a = comp.navItems; // assigning a to the result of a function call!
const b = comp.navItems: // assigning b to a NEW function call's return
console.log(a === b); // false. Two separate arrays are created here. Two distinct objects. 

To resolve this issue, consider implementing trackByFunction as recommended earlier. This approach ensures that only new or modified items trigger a re-render. Avoid returning a new array every time unless the data has genuinely changed. If you prefer dynamically constructing the array within the getter, think about utilizing ChangeDetectionStrategy.OnPush in your component. Then, utilize ChangeDetectorRef.markForCheck() to signal that component data has been updated and requires checking during change detection. Neglecting this step will prevent Angular from re-rendering the component unless any Input properties alter.

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

Setting up Jest to run in WebStorm for a Vue project with TypeScript can be done through

I am struggling to run all my tests within WebStorm. I set up a project with Babel, TypeScript, and Vue using vue-cli 3.0.0-rc3. My run configuration looks like this: https://i.stack.imgur.com/7H0x3.png Unfortunately, I encountered the following error: ...

What is the best way to transform a for loop using array.slice into a for-of loop or map function in order to generate columns and rows

Experiencing an issue with Angular8. Seeking help to convert a for loop for an array into a loop utilizing either for-of or array.map. The code in question involves passing an array of objects and the need to separate it into col and row arrays for visual ...

Localization of labels and buttons in Angular Owl Date Time Picker is not supported

When using the Owl Date Time Picker, I noticed that the From and To labels, as well as the Set and Cancel buttons are not being localized. Here is the code snippet I am using to specify the locale: constructor( private dateTimeAdapter: DateTimeAdapter&l ...

The Angular Library encountered an error stating that the export 'X' imported as 'X' could not be found in 'my-pkg/my-lib'. It is possible that the library's exports include MyLibComponent, MyLibModule, and MyLibService

I have been attempting to bundle a group of classes into an Angular Library. I diligently followed the instructions provided at: https://angular.io/guide/creating-libraries: ng new my-pkg --no-create-application cd my-pkg ng generate library my-lib Subseq ...

An issue occurred with building deployments on Vercel due to a typing error

When attempting to deploy my build on Vercel, I encountered an error. The deployment works fine locally, but when trying to build it on vercel, the following error occurs: [![Type error: Type '(ref: HTMLDivElement | null) => HTMLDivElement | null&a ...

Exploring PrimeNG's method for expanding and collapsing groups

I'm attempting to incorporate two buttons that can be used to either expand or collapse all the groups in my code utilizing primeNG. Below is the functioning code: PLUNKER <p-dataTable [value]="data" sortField="room" rowGroupMode="subheader" grou ...

Encountered an issue when attempting to include a model in sequelize-typescript

I've been attempting to incorporate a model using sequelize-typescript: type AppMetaDataAttributes = { id: string; name: string; version: string; createdAt: string; updatedAt: string; }; type AppMetaDataCreationAttributes = Optional<App ...

What advantages do interfaces as data types offer in Angular compared to using classes?

After watching a tutorial from my teacher, he showed us this code snippet: He mentioned that the products array, defined as type any [], is not taking advantage of TypeScript's strongly typing. He suggested using an INTERFACE instead. I have a questi ...

I'm experiencing an issue with my website where it appears broken when I build it, but functions properly when I use npm run dev in Next

For my project, I have utilized NextJs, Tailwind, React, and Typescript. It is all set and ready to be hosted. After using "output: export" in next.config.js and running npm run build, the process was successful. However, when viewing my website locally, I ...

Implementing the handling of multiple button events in a ListView through onclick function

Currently, I have a listview with three buttons that need to trigger the same method checkInstall on multiple button clicks. However, I am unsure of how to achieve this. Below is the relevant code snippet: html file: <ListView [items]="allAppsList" c ...

Passing parameters to an Angular CLI ejected app

Currently, I am utilizing @angular/[email protected]. I have leveraged the new eject feature to modify the webpack configuration. Now, I find myself perplexed on how to pass parameters to my build process. For instance, how can I execute ng build --p ...

Error encountered when utilizing cursor in Prisma

I am currently utilizing Prisma version 4.2.1 within a Next.js API Route to implement cursor-based pagination for posts. Upon passing the cursor to the API endpoint, I encounter an error message (500) in the console: TypeError: Cannot read properties of u ...

What is the best way to define a global variable in TypeScript and access it throughout a Vue application?

In my main.ts file, I am looking to define a variable that can be accessed in all Vue files. Within my sfc.d.ts file, the following content is included: declare module '*.vue' { import Vue from 'vue' export default Vue } declar ...

How to retrieve a stored value using Ionic 3 native storage

Hey there, I recently attempted to implement code from the Native Storage plugin documentation found at this link: Native Storage import { NativeStorage } from '@ionic-native/native-storage'; constructor(private nativeStorage: NativeStorag ...

Tips for monitoring and automatically reloading ts-node when there are changes in TypeScript files

I'm experimenting with setting up a development server for my TypeScript and Angular application without having to transpile the .ts files every time. After some research, I discovered that I am able to run .ts files using ts-node, but I also want th ...

Is it necessary to track alterations in global service provider in Angular?

I'm working on developing a web application using firebase and angular that requires authentication. As far as I know, after a successful authentication, firebase sends only specific limited attributes to the client (such as displayName, PhotoURL, em ...

Displaying properties of a class in Typescript using a default getter: Simplified guide

Here is an interface and a class that I am working with: export interface ISample { propA: string; propB: string; } export class Sample { private props = {} as ISample; public get propA(): string { return this.props.propA; } public se ...

Not all properties are available in an Angular component reference

I am attempting to create a component that can modify another component by passing a reference to it. Here is my approach: .html : <my-component #myComponent></my-component> <mk-on-boarding [connectedTo]="myComponent"></mk-on-boar ...

Unable to determine all parameters for MatProgressSpinner

I am trying to incorporate Angular Material's Progress spinner component into my project. However, I am facing an issue when importing the module. import {MatProgressSpinnerModule} from '@angular/material'; The error message displayed in t ...

What's the deal with TypeScript tsconfig allowing .json imports, but not allowing them in built .js files?

By including the line "resolveJsonModule": true in my project's .tsconfig file, I have successfully implemented direct importing of data from .json files. The project functions properly, even when using nodemon. However, upon building the project and ...