Understanding Angular's ngFor Directive

This guide explores the core ngFor directive in Angular, covering the following topics:

  • The purpose and syntax of ngFor
  • Common pitfalls and errors when working with ngFor
  • Understanding variable scope within loops
  • Accessing the index of an element in a list
  • Applying alternating styles with even and odd
  • Detecting the first and last items in a collection
  • Default item tracking mechanism and its performance implications
  • Implementing custom tracking with trackBy
  • Determining when trackBy becomes necessary
  • Using ngFor with other JavaScript Iterables, not just arrays

Let's dive into the details of ngFor. A video version of this material is also available below, and the working code examples can be found on GitHub.

Note: If you're looking for the newer @for syntax instead of ngIf, please refer to our separate guide: Angular @for: Complete Guide.

The features of ngFor are also demonstrated in this accompanying video:

Practical Applications of ngFor

The ngFor directive is fundamental for rendering data-driven lists and tables within your HTML templates. For instance, consider a collection of data. ngFor enables us to display this data as an HTML table by generating the necessary markup dynamically.

The ngFor Syntax

To begin using ngFor, we first establish a component with a template. This template then renders the desired HTML output. The typical syntax for ngFor involves several key components:

  • An iteration expression is provided to ngFor.
  • A loop variable, such as hero, is declared using the let keyword, similar to standard JavaScript.
  • The expression follows the pattern let item of items, which aligns with JavaScript's iteration protocol.

Scope of Loop Variables

It's important to note that the loop variable, for example hero, is scoped strictly within the ngFor block. It is not accessible anywhere else in the template.

Common ngFor Mistakes

Developers coming from an AngularJS background often encounter a specific error related to ngFor until they adapt to the new syntax:

Can't bind to 'ngFor' since it isn't a known property of 'tr'

This error typically arises from using item in items instead of the correct item of items, or from omitting the let keyword at the start of the expression.

Retrieving the Index of an Element

A frequent need is to display the numeric position of each item. This can be achieved using the index variable provided by ngFor:

Remember to use the let keyword when accessing the index. Forgetting this results in an error like the one shown here:

Parser Error: Unexpected token = at column ...

With this adjustment, the generated HTML will now include the index for each element.

Creating Striped Tables with even and odd

A common table design involves applying alternating CSS classes to rows and columns. To achieve this, ngFor exposes the even and odd boolean variables. These can be used with ngClass to apply a specific class to alternating rows or columns.

For example, we could add an even class to even-indexed rows and an odd class to odd-indexed rows. The resulting HTML shows that ngClass correctly applies these classes.

Identifying the first and last Elements

Similar to even and odd, ngFor offers two more special variables: first and last. These are boolean flags that indicate if the current item is the first or last in the list.

This allows for targeted styling, such as adding a first or last CSS class to the respective element.

How ngFor Handles List Modifications

ngFor is designed to be efficient. When the input list is modified, it does not rebuild the entire DOM from scratch. Instead, it attempts to reuse existing DOM elements where possible, updating only the necessary parts of the DOM.

This optimization is based on evaluating each item individually. To reuse elements effectively, Angular must be able to identify each item uniquely. This is particularly important when the order of the list changes, so that elements can be re-ordered without being destroyed and recreated.

Default Item Tracking Mechanism

By default, ngFor tracks list items by their object identity. If a new list is created with objects that have the same values as the previous list, Angular cannot recognize them as the same items. From the perspective of object identity, it treats the new list as an entirely different set of objects.

This can happen, for example, when data is re-fetched from a backend. Using object identity as the default tracking strategy is a safe choice because Angular has no inherent knowledge of the object's structure and cannot determine which property should be used for identification.

Performance Implications of Tracking

While ngFor includes optimizations for reusing DOM elements, these optimizations rely on object identity. In scenarios with large lists or lists that render a significant portion of the UI, this approach might still lead to performance bottlenecks. The process of creating and destroying many DOM elements can be slow.

In such cases, you can configure ngFor to use a different tracking mechanism to improve performance further.

Implementing Custom Tracking with trackBy

The trackBy feature allows us to define a custom logic for item tracking. This involves passing a function to trackBy, which receives the item's index and the item itself as arguments.

This function can then return a unique identifier for each object, such as an id property, enabling Angular to track items more effectively.

When to Adopt trackBy

trackBy is primarily a performance optimization strategy. It's not a default requirement, but rather a tool for when performance issues arise. It may be beneficial to apply trackBy to larger tables as a precaution, especially when targeting older browsers like early versions of IE or on low-end devices.

The decision to use it ultimately depends on your specific application and its performance characteristics.

Beyond Arrays: Other Iterables

While we've worked with arrays of JavaScript objects, ngFor is not limited to them. Any JavaScript Iterable can be passed to ngFor, including those created by the framework itself. To illustrate this, let's define a directive for a configuration element, which we'll call hero:

This configuration element can then be used within our template. By using @ContentChildren to query these elements, we can obtain them as a QueryList.

Interestingly, the QueryList is an Angular-specific class that is inherently an Iterable. This means it can be passed directly to ngFor for iteration, even though it's also usable programmatically within a component class.

The same principle applies to any other Iterable within your application.