How ngFor in Angular Works: Deep Dive into NgForOf and Change Detection

The ngFor directive in Angular works by using the NgForOf class to iterate over collections, computing differences via IterableDiffers to minimize DOM updates, and instantiating EmbeddedViewRef templates for each item.

The ngFor directive is Angular's solution for rendering lists in templates. When you use the *ngFor microsyntax in the angular/angular repository, the compiler transforms your shorthand into a full <ng-template> structure backed by the NgForOf directive. This structural directive leverages Angular's change detection system to efficiently synchronize the UI with your data model.

How Angular Compiles the *ngFor Microsyntax

When the Angular compiler encounters *ngFor in your template, it expands the shorthand into an explicit <ng-template> element. The compiler attaches the NgForOf directive to this template during the parsing phase.

According to the Angular source code, this transformation happens in packages/compiler/src/template_parser/template_parser.ts. The parser recognizes the microsyntax and generates the appropriate directive bindings.

Core Implementation: The NgForOf Directive

The actual implementation of ngFor in Angular resides in packages/core/src/common/directives/ng_for_of.ts. The NgForOf class serves as the engine that drives list rendering.

Input Properties

The directive receives three critical inputs:

  • ngForOf: The collection (array, map, set, or other Iterable) to iterate over
  • ngForTrackBy: An optional function that returns unique identifiers for items
  • ngForTemplate: The embedded view template defining the UI for each iteration

At runtime, NgForOf monitors the ngForOf input for changes during every change detection cycle.

IterableDiffers and Change Detection

To determine what changed between updates, NgForOf utilizes the IterableDiffers service located in packages/core/src/change_detection/differs/iterable_differs.ts. This service computes the minimal set of operations—additions, removals, moves, and identity changes—required to transition from the previous collection state to the current one.

The differencing process follows these steps:

  1. Collection Comparison: Compare the current iterable against the previous state
  2. Change Identification: Identify which items were added, removed, or moved
  3. View Reconciliation: Apply these changes to the DOM view hierarchy

View Management and Local Variables

Based on the diff results, NgForOf manages EmbeddedViewRef instances. For each item in the collection, the directive creates a new embedded view or reuses existing ones when trackBy is implemented.

Each view receives a context object containing:

  • $implicit: The current item value
  • index: The zero-based index of the current iteration
  • first: Boolean indicating if this is the first item
  • last: Boolean indicating if this is the last item
  • even: Boolean true for even indices (0, 2, 4...)
  • odd: Boolean true for odd indices (1, 3, 5...)

The low-level rendering instructions that create these views are found in packages/core/src/render3/instructions/template.ts.

Optimizing Performance with trackBy

The trackBy function (bound via ngForTrackBy) is critical for performance with large lists. This function receives the current index and item, returning a stable identifier (such as a primary key).

When trackBy is provided, Angular can match DOM nodes to data items across change detection cycles. This prevents unnecessary destruction and recreation of DOM elements when the reference to the item changes but the identity remains the same.

Practical Implementation Examples

Here is how ngFor in Angular appears in templates:

<ul>
  <!-- Simple iteration -->
  <li *ngFor="let user of users">{{ user.name }}</li>

  <!-- Using index and local variables -->
  <li *ngFor="let user of users; let i = index; let odd = odd">
    {{ i }} – {{ user.name }} <span *ngIf="odd">(odd)</span>
  </li>

  <!-- TrackBy for better performance with large lists -->
  <li *ngFor="let user of users; trackBy: trackByUserId">
    {{ user.name }}
  </li>
</ul>

And the corresponding component implementation:

@Component({ ... })
export class UserListComponent {
  users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];

  trackByUserId(index: number, user: { id: number }): number {
    return user.id;   // unique identifier for each user
  }
}

Summary

  • ngFor in Angular is implemented by the NgForOf class in packages/core/src/common/directives/ng_for_of.ts
  • The compiler expands *ngFor microsyntax into <ng-template> elements during the build process
  • IterableDiffers computes minimal changes between collection updates to optimize DOM manipulation
  • Each iteration receives a context with $implicit, index, first, last, even, and odd properties
  • trackBy functions improve rendering performance by enabling DOM node reuse across change detection cycles
  • View creation uses EmbeddedViewRef instances managed through render3 instructions in packages/core/src/render3/instructions/template.ts

Frequently Asked Questions

What is the difference between *ngFor and ngForOf?

*ngFor is the microsyntax shorthand you write in templates, while ngForOf is the actual directive class that implements the functionality. When you write *ngFor="let item of items", the Angular compiler desugars this into an <ng-template> with the ngForOf input property bound to your collection. The asterisk signals to the compiler that this is a structural directive requiring template expansion.

When should I use the trackBy function with ngFor?

Use trackBy whenever you render large lists or when your collection references change frequently while the underlying data remains the same. Without trackBy, Angular destroys and recreates DOM nodes whenever the item reference changes, even if the data identity is identical. By returning a stable ID (like a database primary key), you allow Angular to reuse existing DOM nodes, significantly improving rendering performance and maintaining component state.

How does ngFor handle immutable data updates?

NgForOf detects changes by object reference comparison during the change detection cycle. If you update an array using methods that preserve the reference (like push()), Angular may not detect the change. To ensure updates trigger re-rendering, create new array references using the spread operator ([...items, newItem]) or immutable update patterns. The IterableDiffers service will then compute the difference and update the views accordingly.

What local variables are available inside an *ngFor template?

Inside an *ngFor template, you have access to several exported local variables: index (current position), first (boolean for first item), last (boolean for last item), even (boolean for even indices), and odd (boolean for odd indices). You assign these using the let syntax, such as *ngFor="let item of items; let i = index; let isFirst = first".

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →