Special Angular team event and more performance tips

For the second consecutive week, I want to experiment with a slightly different newsletter format inspired by James Clear’s 3-2-1 weekly newsletter. I aim to give you three short articles about Angular, two quick Angular ecosystem updates, and one question to make you think differently about the code you work with every day.

Three short articles to revisit:

Read this short challenge on Angular template syntax and then look at the solution and explanations here, here, and here.

Two updates to know about:

  • The Angular team is hosting a special event on YouTube today at 11 a.m. US/Pacific. If you miss the live event, a recording will be available shortly after.
  • A new feature called linkedSignal is in the works. So far, it seems similar to a computed signal that is also writable and only depends on one source signal.

One question to think about:

Do you know when to use the OnPush change detection strategy and how to differentiate container components from presentation components?

Let me know what you think about this different newsletter format. As always, it remains short and to the point. Also, if you have announcements you’d like to share in the newsletter, let me know.

Syntax tricks for Angular HTML attribute bindings

What’s great about Angular is that the framework provides many different features out of the box. Some are well documented and widely known, while others are more obscure and less commonly used, though very useful.

For instance, there’s a ngClass directive to dynamically set classes to an element based on conditions:

In the above example, the button will have an active-btn CSS class if isActive is true, and a disabled-btn CSS class if isDisabled is true. That’s what the directive does.

But the thing is… We don’t need a directive to do that. I never use ngClass.

Instead, I do this:

It works the same way, and I’d argue that the syntax is slightly easier to understand. No directive needed.

On a different note, if you’ve ever tried to set the values of non-HTML attributes to an element, such as data-test for unit or end-to-end testing, you would find out that this syntax doesn’t work: [data-test]="value"

For these “unknown” HTML attributes, we need to use the following syntax: [attr.data-test]="value"

And then all is fine.

How to migrate Angular syntax to the latest features?

I posted about how to update your version of Angular a while back. Still, with the ongoing updates in the framework, such as standalone components, the new control flow syntax, and the inject() function, there are quite a few new options that aren’t required but can still be adopted widely.

For instance, if you want to remove your ngModules and go full-standalone; there’s a migration command for that:

This command will ask you about different options, such as removing unnecessary ngModules, and switching your AppComponent to standalone bootstrapping.

If you want to get rid of NgFor, NgIf, and the like, you can migrate automatically to the new control flow syntax with:

Another migration command was added in Angular 18.2. This one is to migrate your dependency injection syntax to use inject() instead of constructors:

Finally, and also added in Angular 18.2, is the migration to use lazy-loading on all routes:

This last command can be applied to a subset of routes by using the path argument:

The official documentation for all these migrations can be found here.

@let for local variables in Angular views

Angular 18.1 brought a new feature to our Angular templates: the ability to create local variables. Just like the new control-flow blocks @if, @for, and @switch, the new syntax uses the @ character with a new keyword: @let.

Here is an example:

The above code displays 21 as the value of the Angular expression. Why is this helpful? In many cases, we deal with complex nested objects that translate into long, verbose expressions:

With @let, we can make things shorter:

Another major use case is when using the async pipe. I covered several tricks on how to use the async pipe in the past, even introducing ngrxLet as an alternative, and all of these tricks can now be replaced with @let.

Here is the initial problem with async when dealing with data from an Observable that we need in several places:

The above syntax is not only horrible, but it also multiplies subscriptions to the source Observable, which is even worse. The common workaround consisted of using a structural directive because such directives allow using local variables as follows:

That’s more readable but still not ideal. Here is what we can do with @let now:

That’s a lot better. If the multiple ? drive you crazy, you can do this instead:

@let variables are read-only and cannot be reassigned. They behave like the const keyword in Javascript. The scope is also to the nearest block (which means view, in the case of Angular templates), so in my above example, addr is only available within the @if block, invisible outside of it.

You can play with my examples on Stackblitz here.

Prefetching with the @defer block

We’ve covered how to use @defer to lazy-load blocks of code in our Angular v17+ applications. We also touched on the different trigger options as well as the ability to create custom triggers with when.

Yet there’s more to uncover with the prefetch option:

The above code would display my-component when the user interacts with the web page, but it would prefetch the code on idle, meaning as soon as the browser isn’t busy doing anything else. That way, when the user starts interacting with the page, the component has already been downloaded (or is currently downloading) from the server, which speeds things up.

The nice thing about prefetch is that it supports the same triggers (idle, viewport, interaction, hover, immediate, timer) as the @defer block, which allows for lots of different possible customizations.

Local variables and the new block syntax

Earlier this year, I mentioned how the async pipe can become much more useful if we use a local variable instead of multiplying subscriptions to an Observable. In this example, we store the data we get from a subscription in the user variable:

While that syntax still works as is, you might want to use the new control flow syntax of Angular 17. Fortunately for us, it is possible to use local variables as well using the following syntax:

The only difference is the ; in the middle of the expression.

Angular 17: Lazy-loading with @defer

Angular 17 is available, and we already covered its new optional control-flow syntax. Today, let’s cover the new option to fully customize lazy-loading with Angular using @defer.

What’s great about @defer is that it does not rely on the Angular router anymore. You can lazy-load any standalone component anywhere, anytime, and on your terms, as you can decide the trigger to load that component.

Let’s start with a basic example:

The above code will load the HelloComponent as soon as the browser is idle, which means it’s done loading everything else. While that is happening, we can decide to display a placeholder, and in my case, I decided that such a placeholder would be displayed for a minimum of 2 seconds no matter what. You can use seconds (s) or milliseconds (ms) as a time unit:

You can see the above code in action here on Stackblitz. Note that the Angular Language service was updated accordingly, so VS Code (Stackblitz is a web-based VS code), Webstorm, and other IDEs already know about the new @defer syntax and do proper highlighting of it!

We can also specify a loading template and an error template, all customizable with a minimum amount of time during which we would display those templates (example on Stackblitz here):

I used “big” numbers to see the different states in my examples. Note that @loading supports an “after ” option only to show the loading template if loading takes more than a certain amount of time, so you don’t have to display anything if the component loads quickly. Both parameters are optional:

These are the different new blocks available with @defer. Tomorrow, we’ll look at the various possible triggers.

Perf and template syntax – Example 3

Yesterday, we looked at the pros and cons of our second code example.

Today, let’s cover the pros and cons of our third and final example:

Example #3 – Signal

<div *ngFor="let data of dataSignal()">{{data.name}}</div>

This last example is very interesting for a few reasons. First, if you follow Angular best practices, you might be screaming that we’re calling a method in a template, and you would discard that code immediately.

But the thing is, this is a Signal, and Signals are different. It’s perfectly fine to call a Signal in a template, and as a matter of fact, this is the way to do it so Angular knows where Signals are read and can then optimize DOM updates based on that information.

Even better, when we use a signal with ngFor, the subscriber to that signal is actually a view (an instance of ng-template) and not the entire component. When Signal-based components become part of the framework, Angular will be able to re-render just that view instead of the entire HTML template of that component.

What are the cons, then?

The only downside of Signals is that they require Angular v16+, so you’ll need to upgrade your apps to use them. The other downside is that the full benefits of improved change detection are not there yet and won’t land in v17 either, but the Angular team is making steady progress, and some early parts of Signal-based components were released in version 16.2.

Other than that, Signals are the way to go. if you’re just getting started with Angular and building a new app, I believe you should use Signals as much as possible.

Perf and template syntax – Example 2

Yesterday, we looked at the pros and cons of our first code example.

Today, let’s cover the pros and cons of our second example:

Example #2 – Observable

<div *ngFor="let data of getData() | async">{{data.name}}</div>

This code is calling a method in a template, too, and that’s a lot worse than in example #1. Here’s why: If the getData() method returns an Observable by calling httpClient.get() (or a service that makes that same call) then the async pipe subscribes to it and receives the data, which triggers change detection, so Angular calls getData() again, gets a new Observable, the async pipe subscribes to it, and there you have an infinite loop of HTTP requests that will bring down your browser for sure, and possibly your server.

How to fix it?

<div *ngFor="let data of data$ | async">

Store the Observable in a variable instead of calling a method, and the problem is solved. You can assign that Observable in the constructor of your component as follows:

constructor(service: DataService) {  
   this.data$ = servie.getData();
}Code language: JavaScript (javascript)

At this point the code is pretty much optimal because:

The only con is the async pipe syntax and working with Observables, which is often considered the most complex part of the Angular learning curve.

Perf and template syntax – Example 1

Yesterday, I sent you 3 different examples of Angular binding syntaxes and asked you to take a look at them and identify the pros and cons of each one.

Today, let’s cover the pros and cons of our first example:

Example #1 – Array of data

<div *ngFor="let data of getData()">

The only pro of this example is the simplicity of the syntax. Other than that, the syntax uses one of the anti-patterns covered earlier in this newsletter: Calling a method in a template. The problem is that this method will be called a lot more than you’d think, and if that code happened to create a new array every single time, it could be really bad in terms of performance, as Angular would have to re-render the entire list pretty much every time the user does anything in the application.

What can we do to improve it?

  1. Use a variable instead of a method – this fixes the performance issue right away:

    <div *ngFor="let data of dataList">
  2. Use a trackBy function to avoid useless re-renders. By telling Angular what’s the unique ID of each list item, Angular would be less confused, but it’s a sub-par solution:

    <div *ngFor="let data of getData(); trackBy: trackById">

Conclusion

If you want to iterate over data that’s in your component (not an observable or a Signal), then use a variable instead of calling a method. Easy enough.