Updates, debugging, and performance

This week, I want to experiment with a slightly different newsletter format inspired by James Clear’s 3-2-1 weekly newsletter. My goal is to give you three quick updates about the Angular ecosystem, two short topics to revisit, and one question to make you think differently about the code you work with daily.

Three updates to know about:

Two short articles to revisit:

How to use the Angular Devtools browser extension and the ng namespace functions for debugging.

One question to think about:

Do you know the size of your build output and regularly check or update your build budgets?

Let me know what you think about this different newsletter format. As always, it remains short and to the point.

Structural directives shorthand syntax

Have you ever wondered what’s behind the following syntax?

Whenever I teach Angular, people end up asking me: “Why is there a * before ngIf and ngFor? If I don’t use the *, the IDE seems to be OK with it.”

Well, yes and no. If you wanted to remove the *, then you would have to write:

In other words, *ngIf is a shorthand syntax for <ng-template [ngIf]>. It’s designed to make our template syntax more manageable and easier to read.

This is especially true if you use local variables from exported directive values. In those scenarios, the “long” syntax is barely readable with the let- declarations:

Note that this syntax trick applies to all directives if you prefix them with *. Here is an example where I created my own structural directive to select tab templates.

An Angular Upgrade Service?

Earlier this month, I polled people on LinkedIn to know which version of Angular they’re currently using at work. Here are the results:

First, I was pleasantly surprised that most of them (58%) are fully up-to-date or just one major version behind. What’s more worrying is that 24% of them use a non-supported version. So I asked why people use older versions of the framework (you can contribute here with your comments if you want), and of course, I knew more or less what the answers would be: Time – Budget – Dependencies.

This made me think about the following: What if there was an Angular Upgrade Service? A hands-on service you could subscribe to that would keep your projects up-to-date. Do you think that would be of interest?

I’m always looking for gaps to fill in the front-end development space. This is why I created the Angular certification program, the React certification program, and just helped launch a Javascript certification program!)

In any case, if you think you could use an Angular Upgrade Service, please let me know (respond to that email or reach out on LinkedIn – always happy to connect!), and I might beta-test that idea with you in the next quarter.

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 use Jest for Angular Unit Tests?

Karma is deprecated, and the Angular team is working on official support for Jest and Web Test Runner. Today, I want to highlight how to set up your Angular project for Jest since it’s prevalent in the Javascript ecosystem (React, etc.). It’s super fast and 100% browserless, making running tests on a continuous integration server easy.

Also, the syntax is essentially the same as Jasmine/Karma, so you won’t have to change your tests much unless you do extensive mocking in Jasmine.

Here are the steps I’ve used on over 28 repositories so far, with great success:

  1. Uninstall Jasmine, Karma, and all associated types
    npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter @types/jasmine jasmine-core

  2. Install Jest, its types, and presets for Angular
    npm i --save-dev jest @types/jest jest-preset-angular

  3. Create a setup-jest.ts file in your project’s root folder
    And add that single line of code in it:
    import 'jest-preset-angular/setup-jest';

  4. Create a jest.config.ts file in your project’s root folder with the following command
    npx jest — init

    Then ensure you have the following config options set in that file:
    preset: 'jest-preset-angular',
    setupFilesAfterEnv: ['./setup-jest.ts']


  5. Update your tsconfig.spec.json file

    Your compilerOptions should look like this:
    "compilerOptions": {
    "outDir": "./out-tsc/spec",
    "types": [ "jest" ],
    "esModuleInterop": true,
    "emitDecoratorMetadata": true
    },


  6. Last step: Update package.json
    In the scripts section, replace the test entry with:
    "test": "jest",

And… that’s it! You can now launch npm run test to run your tests with Jest. Sure, a schematic would be better, but those steps take only 2-3 minutes to implement for now.

How to use toSignal() outside of an injection context?

If you’ve ever tried to convert an Observable into a Signal using the toSignal() function, you might have encountered the following error NG0203: toSignal() can only be used within an injection context:

This happens when you create an Observable outside of an injection context, which can happen if you want to download data when the user clicks on a button, for instance:

Can we make this work? Absolutely! The key is to access our injector and store it in a variable so we can pass it as a parameter to toSignal(). How to do that?

Interestingly enough, we can access our injector by… injecting it into our component:

Then, we pass it as an option to the toSignal() function:

And that’s it, problem solved! You can see the full code example on Stackblitz.

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.

When you don’t need template-driven or reactive forms

A common misconception shared by many Angular developers is that if you have a form, you need to use template-driven or reactive forms to handle it.

The truth is, in most cases, simple forms don’t need any of that. Instead, you can use template reference variables. My favorite example is a login form:

The two template reference variables used in the above example are sufficient to capture the username and password when the user clicks the log-in button. There is no need for extra complexity!

We lose some features along the way, such as reactivity to changes and automatic form validation. For instance, if I use this expression, the value will not change unless the component gets refreshed by another change:

This is because no Angular event listener triggers change detection when the form input value changes. But again, for my example of the log-in form, it doesn’t matter.

You can check out a live example here where I capture updates when the user clicks a button to emulate some reactivity. For anything more complex than such an example, using template-driven or reactive forms makes perfect sense.

If you want to learn more about similar tips and tricks through code challenges delivered to your inbox twice a week, check out the Angular Accelerator program. It’s currently open for 5 free scholarships, and there’s a 5-day free trial no matter what!

FormGroup: All you need to know

Recently, during an Angular Accelerator coaching call, my client asked what is the point of using FormGroup. I then realized I had never written about FormGroup, so here is a post to fix that.

When using reactive forms, we always have at least one FormGroup, which happens to be the entire form. This doesn’t mean that Form Groups are only designed for that purpose: A complex form can be divided into multiple groups. For instance, if we have a form where a user is supposed to enter their name, address, birth date, etc., we could group some of these controls into FormGroups.

What do we get out of that? A few different things:

  1. Form groups aggregate the validation state of all individual controls into a group state. If an address is made of 4 controls (street, city, zip code, country), then the form group for these 4 controls will be invalid when any controls are invalid, touched when any control is touched, etc.
  2. Form groups aggregate actions on all individual controls within the group. Want to disable address entry? Instead of disabling all 4 controls one by one, you can do addressGroup.disable() , which is a lot less error-prone (you can’t forget one of these controls)
  3. Form groups store the value of all controls as a single object. In my example with 4 controls for an address, accessing addressGroup.getValue() would return:

So, the main benefit is to have grouped actions and status updates for a bunch of form controls. Let’s take another example with actual code (you can find the full code base on Stackblitz here):

If I use the following expression in my template to see the value of my userInfo, here’s what I get:

As you can see, my form group creates an object that updates when individual control values change. Perfect! Now, let’s click a button that sets the form group value to an object:

Now, I can use multiple values with one line of code. There is no need to call setValue() on all controls one by one. That’s cool. What if I want to apply a “diff” to my group and not set the entire thing? Say I wanted to be younger but not change my name or anything else:

Now, I can “patch” my form group with some localized changes, and I don’t need to worry about the other values of the controls of that group. That’s the magic of formGroup.patchValue().

You can also reset the entire group with formGroup.reset(), add or remove controls and validators, and more. The full API is here. That’s the power of FormGroups in action!

What’s new in Angular 18.1?

Last week, I covered the new @let syntax to create local variables in our HTML templates.

Let’s review some other notable updates from Angular 18.1.

toSignal() custom equality function

Remember the toSignal function to convert an Observable into a Signal? This function now supports an equal parameter to pass a custom equality function. You can read this tutorial on Signal equality functions for more information and examples.

RouterLink with UrlTree

You’ve probably used UrlTree objects in Angular route guards to implement redirects when a user isn’t logged in, for instance. It is now possible to use UrlTree with RouterLink, too, so you can do something like this:

After defining userPath as a UrlTree as follows:

Diagnostic for uninvoked functions

An added diagnostic throws an Angular error if you make a mistake in your template syntax. If you do (click)="save" instead of (click)="save()".

There are some other changes under the hood and some updates to unstable APIs, but I’ll cover those when the changes are finalized and become stable.