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 otherIterable) to iterate overngForTrackBy: An optional function that returns unique identifiers for itemsngForTemplate: 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:
- Collection Comparison: Compare the current iterable against the previous state
- Change Identification: Identify which items were added, removed, or moved
- 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 valueindex: The zero-based index of the current iterationfirst: Boolean indicating if this is the first itemlast: Boolean indicating if this is the last itemeven: 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
NgForOfclass inpackages/core/src/common/directives/ng_for_of.ts - The compiler expands
*ngFormicrosyntax 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, andoddproperties - trackBy functions improve rendering performance by enabling DOM node reuse across change detection cycles
- View creation uses
EmbeddedViewRefinstances managed through render3 instructions inpackages/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →