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.

Performance and template syntax

Today, I’d like to challenge you with a small exercise. Take a minute to examine the three following code examples and decide which option performs best. Note how similar those three options are from a syntax standpoint, yet they yield very different results performance-wise:

Example #1 – Array of data

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

Example #2 – Observable

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

Example #3 – Signal

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

Feel free to list the pros and cons of each approach. You can even respond to that email with your answers if you want. Tomorrow, I’ll give you my insights.

Angular Change Detection Illustrated

I see a lot of confusion around Angular change detection. I compared the OnPush and Default strategies in a past post, but let’s illustrate them with visuals.

Default Change Detection

An event happening anywhere in the DOM tree will have Angular check the entire tree for changes:

OnPush Change Detection

If a hierarchy of components is using OnPush, then only that branch on OnPush components will be checked for changes:

Signal Change Detection

This is the future of Angular. When using Signals, only the views of components that use that Signal will get updated, making it the best and most accurate option:

Creating a library with Angular

You’re probably used to creating Angular applications, but what about Angular libraries?

First, it’s essential to define what an Angular library is. A library is a collection of components/pipes/directives or services bundled together that can be shared and used in multiple code repositories. As a result, Angular libraries are designed to be published on npm (publicly or privately) so they can be shared with other people externally or internally.

If you want to start a new library from scratch, these Angular CLI commands will get it done:

The above commands create a projects/my-lib folder in the workspace, with a sample component and service in it. The main difference between a library and an application is that a library exposes public features that can be imported into other libraries or applications.

Such features are listed and exported in public-api.ts in the library folder. That’s where you decide what’s public/private in your library code. In this example, the library is just one service:

Then, to test or build your library, you can use regular Angular CLI commands such as:

Once built, a library can be published to the public npm repository with one single command. This command has to be run from the dist folder where the compiled library can be found after running your production build:

Note that this command requires an npm account and npm authentication before you can publish. Upon publishing, the version number used in your package.json will be used as the public version number on npm:

And on npm’s website:

At that point, anyone can run npm install [your-library-name] and use your code in their projects. Nice and easy!

Note that the library I used in that example is a work in progress and should not be used as-is in your apps.

Angular 17 is coming soon…

Angular 17 tentative release date is set for November 8, 2023. Be ready for a few good surprises along the way, but for now, I can share that the new control flow features will be available with that version.

There was a control flow RFC a few months back. The Angular team received valuable feedback from it and decided to go with the following syntax:

The above syntax can be used instead of ngIf and its awkward else syntax. Also visible in the above example is @defer, a way to lazy-load a component based on different criteria.

Of course, we’ll cover all this in the newsletter in November once all these features are officially released. In the meantime, mark your calendar: November 8 is the date.

Fine-tuning your eslint configuration

Last week, we introduced eslint and how it can help improve our code by identifying places where we have dead code or don’t follow best practices. Sometimes, we “break” some of these rules on purpose or decide to adopt a different convention, which is perfectly fine.

In that case, instead of giving up on eslint entirely, a better idea is to change its configuration to tweak the severity of a rule or even disable it. An es lint rule has three different severity settings:

  • “off” or 0 – turns the rule off
  • “warn” or 1 – turns the rule on as a warning (doesn’t make the lint command fail)
  • “error” or 2 – turns the rule into an error (makes the lint command fail with exit code 1 – a good option to fail a continuous integration build)

Such severity tweaks can be made in the .eslintrc.json file created in your project by the Angular schematics:

In the above example, I made the first two rules throw an error instead of a warning (I’m very much against disabling type-checking in TypeScript), but I’m OK with seeing some var keywords instead of let, so I turned off that third rule.

Getting the rule’s name is easy: When the linter fails, that name will be displayed in the console. Here @typescript-eslint/no-empty-function :

Some rules accept more configuration options to create an allowlist of accepted values. For instance, @angular-eslint/no-input-rename prevents you from renaming @Input values, but you can specify a config option that allows a few input names:

The config for that rule becomes an object that looks like this:

The above config allows renaming inputs only if the new name is check or test. This gives you more flexibility than turning off a rule entirely if it’s too restrictive for you.

Improve your code with eslint

eslint is a popular linter that parses your code and outputs a list of warnings and errors to help you improve. The library is designed to lint JavaScript code, and there are extra plugins for TypeScript and Angular so we can get even more specific feedback for our components and services. Here is an example of linting output:

A linter is a perfect complement to a compiler. For instance, angular-eslint, the eslint plugin for Angular, will also look at your HTML templates and flag code that doesn’t follow the Angular style guide. It’s also looking for possible mistakes, such as getting the ngModel 2-way data-binding syntax wrong:

To give you a better idea, here is the list of all the template rules and all the Angular TypeScript rules. If you want to give eslint a try, the first step is to install it with the help of some schematics:

This will download the proper dependencies and plugins and set up everything necessary to lint your code. If you’re using an older version of Angular or building a library instead of an app, there are step-by-step instructions to follow here. Once the set-up is done, all you have to do is run:

This command will parse all your files and output feedback in the console. Note that several IDEs can detect your eslint config and suggest automatic fixes to linting errors, which is even better!

Directive Composition API

In the past few weeks, we’ve covered different examples of directives, such as the CDK Listbox directive and the CDK Drag and Drop directive. I have mentioned that directives are underused in Angular applications these days, and one way to use more directives is to adopt the directive composition API.

Let’s consider our previous example of a list of items:

Let’s assume we want to make our list flexible enough to support re-ordering items using drag and drop. To do so, we could start using multiple different directives on the same element, such as cdkOption and cdkDrag (code example here on Stackblitz):

While the above code works, it’s designed for a one-time use case. If we know that our application will use several droplists that support drag-and-drop, we should start thinking about creating our own custom directive that refactors these different features in one place. Enter the directive composition API:

This new directive draggableOption is composed of both cdkOption and cdkDrag to achieve the desired result. We have one input text that is forwarded to the cdkOption input thanks to this syntax:

Note that both inputs and outputs can be forwarded that way (code examples here). The beauty of this approach is that our new directive has very little code while packing reusable features in a concise syntax. This is how we would use our new directive:

You can find that example live on Stackblitz here. There are a few caveats with the directive composition API, mainly that it only works with standalone directives and that the options to “forward” inputs and outputs are limited for now. However, the composition API is an excellent option to make our code more reusable and immune to copying and pasting collections of directives from one component to another.

How to handle drag-and-drop with Angular?

Drag and drop features can dramatically improve the UX of a web application. Implementing drag and drop with Javascript seems intimidating, which is why we have the Angular CDK (Component Development Kit).

Let’s say we have a list of items we want to be able to reorder using drag and drop:

Thanks to the CDK drag-and-drop directives, such a task is easy. Here’s all the code needed to enable it:

The two directives cdkDropList and cdkDrag enable the drag-and-drop feature. We also need the following code in our component class:

The moveItemInArray function is a utility function from the CDK, so we don’t have to worry about that.

See the source code and try this example in action on Stackblitz here.