ngFor trackBy for improved performance

We all use the ngFor directive on a daily basis. The directive is used to repeat a template as many times as needed based on an array of data we want to render. We covered how the directive exposes useful local variables in the past.

To help Angular perform DOM updates when items in the array are reordered, new items are added, or existing items are removed, we can add a trackBy attribute that specifies what unicity criteria Angular will use to track these additions/removals/reorderings:

Of course, this only helps if the array in question is subject to changes in the future. By default, Angular uses Object.is() to compare values in the array, which is somewhat similar to a === comparison.

In our case, we created a function that specifies how to track by _id:

That way, Angular will know how to compare objects efficiently and won’t perform DOM updates that aren’t needed, which can be a huge performance boost if you’re displaying a massive data table with thousands of records. You can see this example in action on Stackblitz here.

ngFor local variables

We use the ngFor directive so often that it’s easy to forget or even ignore some of its most powerful features.

For instance, it’s fairly common to access the index of an item in the array we’re working with, but there are five more local variables that can be used:

  • index: number: The index of the current item in the iterable.
  • count: number: The length of the iterable.
  • first: boolean: True when the item is the first item in the iterable.
  • last: boolean: True when the item is the last item in the iterable.
  • even: boolean: True when the item has an even index in the iterable.
  • odd: boolean: True when the item has an odd index in the iterable.

The syntax to use those local variables is as follows – and you can use as many as you want in a single ngFor directive like so:

Using a loading template with ngrxLet

A while back, I wrote about how ngrxLet is an improved version of the async pipe. I also covered how to use skeleton loaders with Angular.

Today, let’s look at how we can use these two tools to display a “loading” template while an observable is waiting for data.

As a reminder, here is how ngrxLet can be used to track different observable events:

In the above code snippet, we receive the values emitted by the number$ observable in a variable n. We would receive any error in a variable e. We would receive the completion status (true or false) in a variable c.

Here is how we can pass a custom loading template to ngrxLet using a template reference variable:

Of course, the loading template can be customized with anything you want, including a skeleton loader or an animated gif image. That feature is called a suspense template, and an observable is in a suspense state until it emits its first event (next, error, or complete as covered here).

HostBinding and HostListener

A few months back, I suggested that Angular developers don’t use enough directives and use too many components. This is because components quickly become second nature when writing Angular code, while directives are less common and, as a result, can feel more complex at first.

The critical difference between components and directives is that components have an HTML template, and directives don’t. That lack of a template can be slightly uncomfortable at first unless you realize that you still have access to HTML attribute bindings but in a different manner.

For instance, let’s consider the following component template:

Those two bindings used in a directive would become:

In other words, when you see a binding with [] in a component, @HostBinding() does the same thing in a directive.

For example: [id]="testId" in a component template becomes @HostBinding("id") testId; in a directive class.

The same goes for event listeners. This component (click) binding:

Becomes the following in a directive:

As a recap: [] become @HostBinding and () become @HostListener. Those are the same thing. That’s it. No template is needed. For a real-life example of a custom directive, feel free to take a look at this tutorial of mine.

Image optimization directive – NgOptimizedImage

The Angular team has always focused on improving the framework by making everything faster, from the compiler to our runtime code that gets optimized, minified, and tree-shaked.

The Image optimization directive was added to Angular 15 in that same spirit.

What it does:

  • Intelligent lazy loading: Images invisible to the user on page load are loaded later when the user scrolls down to that page section.
  • Prioritization of critical images: Load the essential images first (header banner, for instance)
  • Optimized configuration for popular image tooling: If you’re using a CDN, the directive will automatically pick the proper image size from that CDN, optimizing download sizes based on how many pixels of that image will be displayed on the screen.
  • Built-in errors and warnings: Besides the above built-in optimizations, the directive also has built-in checks to ensure developers have followed the recommended best practices in the image markup.

All you have to do to get all these benefits is to use the ngSrc attribute instead of just src:

For CDN optimization, you can use one the 4 existing providers (or create your own) so that the proper image size is always requested. In my example, I use the Imgix CDN, so my config looks like this:

From that information alone, we can tell that Angular was able to generate the proper image URLs to fetch the smallest image possible to fit our div – no more downloading of a 2000 x 1000 pixels image if all you need is 200 x 100:

The NgOptimizedImage directive is part of the @angular/common module, just like ngFor and ngIf, so it’s already part of your toolkit if you use those directives.

It can also be used as a standalone directive without importing CommonModule. My example is on Stackblitz here. The official documentation and more information about that directive can be found here.

The power of Angular selectors

Yesterday, we saw how using directives instead of components can increase code reusability by not tying our code logic to a specific HTML template.

Now, if we want to write some code once and for all and make it work for all HTML elements in our app that match some specific conditions, we have another underused tool at our disposal: CSS selectors.

For instance, let’s say we want the isActive directive from our previous example to be applied to all buttons in our application. Of course, we could manually look for all buttons and add the isActive attribute to them, but that would be tedious and error-prone.

Or we could be more imaginative and change the selector of that directive so that it applies to all buttons and any other elements that have an isActive attribute:

With that implementation, all current (and future) buttons will have that directive applied to them. But what if we want to make any exceptions and disable the directive on some buttons? That’s how we could do it:

Then all we would need is to add the disableIsActive attribute to those buttons that don’t want the isActive directive:

My point here is that there are some creative use cases for CSS selectors, and those selectors are the same as those used for CSS styling, meaning that we can rely on classes, attributes, element names, or any combination of the above!

The Angular framework does precisely that in quite a few places. If you’ve ever wondered how forms get auto-magically handled by Angular, now you know the answer. The ngForm directive has a complex selector that would apply to all form elements by default:

When to create a directive vs. a component?

Most Angular applications are composed of a majority of components and just a few directives if any. Today, I’d like to show you an example where a directive makes much more sense than a component.

Consider the following component template:

We have created a component tied to one single HTML element (button), and all the logic in that template is about changing attribute values (for the isActive class and the disabled attribute).

To achieve this, we needed three specialized inputs that resulted in a loss of reusability:

The above isn’t ideal because:

  • Our button can only display text, not any HTML (so no icons, for instance)
  • Our button assumes that its state always depends on saved and hasError, but what if we want to add a third variable to the mix for other use cases?

Let’s create a directive instead:

Which would be used like so:

This implementation is a lot better because:

  • It would work with any HTML element, not just buttons (assuming the element supports disabled properly)
  • The button can display anything, not just text
  • The criteria for isActive to be true depends on the use case and isn’t tied to just saved and hasError.

The point here is that using a directive in that scenario made our code more flexible, and as a result, more reusable. That’s why popular component libraries rely heavily on directives rather than components. Here’s an example from the Angular Material documentation:

You can see that mat-button is a directive, not a component. That way, it can be used with a button or an a tag. You can find the example code of this post on Stackblitz.

Think about this the next time you’re tempted to create a component with one or two lines of HTML in its template. Most likely, what you need instead is a directive.

Check out this tutorial for another example of a directive that makes perfect sense instead of using a component.

Exported Directives

Yesterday, we talked about Template Reference Variables. Today, I want to show you how a directive can be accessed with a Template Reference Variable.

You’ve probably seen that syntax before:

<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)" >Code language: HTML, XML (xml)

That syntax is possible because the NgForm directive is exported using the following syntax (actual Angular source code here):

@Directive({
   exportAs: 'ngForm'
})Code language: TypeScript (typescript)

The above code enables the usage of Template Reference Variables such as #myForm="ngForm" . This technique is widely used in Angular forms and component libraries to expose public directive properties (and methods) to your component’s template.

For instance, we can access myForm.value or myForm.valid in an expression.

ngModel is exported that way, too.