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.

@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.

3 pieces to discover or read again this summer

It’s early July, and in several countries, it’s a time for vacation, relaxation, and even celebration, like for us in the United States, with the 4th of July holiday to celebrate the country’s declaration of independence.

As a result, it’s a good time to reflect on the content published during the first half of the year.

Here are my three most popular posts of 2024 so far – feel free to discover them or reread them, as there’s a lot of important content in those three:

  1. Angular signal-based components tutorial: No surprise, as this is the future of Angular applications. If you’re more into video content and live coding, you can watch this recording of my workshop at FrontEnd Nation in June: Part 1Part 2.
  2. Introduction to server-side rendering with Angular. Server-side rendering has never been this easy with Angular, so I always recommend looking into it and being curious about it.
  3. How to use NgRx SignalStore? Signals are all the rage now, and NgRx released a state-management feature built around signals. The learning curve is much better than that of regular NgRx.

Debugging without breakpoints

I covered the Angular Devtools extension in a newsletter entry last year, and this browser extension keeps getting better and better.

For instance, with Angular 17, an injector tree view feature was added.

With the addition of signals to the framework, we get even better insights and access to our data in a static way that doesn’t require any breakpoint. For instance, if you want to see what’s the current value of a signal in one of your components, the extension shows that value:

We can tell right away that title is a Signal and that the current value is “Welcome to our store”. With Observables, we don’t have that luxury:

So that’s one more good reason to use signals in our Angular applications and perhaps one more reason to try the official Angular dev tools extension if you haven’t done so yet.

Another cool feature is that when you select a component in the tree view, you can see the text "== $ng0" showing up next to the component name:

This $ng0 is a variable that can be explored in the browser’s console. If I type $ng0 there, here’s what I get:

I can explore that component and any service injected into it right from the browser’s console—no breakpoint is needed.

Bonus tip: If you selected another component previously, it is now known as $ng1. The one before that, $ng2, and so on.

Tutorial: Architecting forms with Signals

In today’s post, I want to showcase several Angular framework features in one example. More specifically, we’ll use standalone components, signals with model() and input(), as well as forms.

Our goal is to build a reusable date-range picker component to select two dates:

To do so, let’s create a standalone component with two HTML date inputs (no need for component libraries!):

We use ngModel (which is from FormsModule) to capture the current value selected by the user and set a default value for those dates if needed. To do so, let’s use two signals created by the model() function from @angular/core:

Two important things to note:

  1. These two variables are signals and work seamlessly with ngModel
  2. These variables enable two-way data bindings with the parent component as follows:

start and end are signals as well. We can use those signals to pass a default value to the child component or keep that value empty if we don’t want any:

And that code is 100% functional as-is. No need for template-driven forms or reactive forms! Now, let’s add some features to our date-range picker component. Let’s say we want to enforce the rule that the start date cannot be after the end date and vice versa. The following bindings do the trick using the min and max HTML attributes:

Because this is all based on signals, it’s all reactive by default. Changing the start date or the end date automatically updates the range of what’s selectable in the other date input:

Adding more features is now as easy as adding more signals and bindings. For instance, I want to enforce a minimum start date. I added a new signal input called minDate and I bind it to the min attribute of our startDate:

And then, in the parent component, we can pass a minimum start date:

You can find that code example in action on Stackblitz here. Note that our date range picker has less than 20 lines of code and relies on basic HTML features (input, min, max) instead of adding dependencies that would impact performance.

Please email me if you’d like me to add more features to that example in a future post. I’m always happy to cover what really matters to most people reading these posts.

Talks on Signals, SSR, and Angular 18

I’m giving several talks at free conferences and events this week, so I thought I would share those with you in case you’re interested. As always, all of these are free for you to enjoy.

Two of those events already have a recording available on YouTube:

Then I’m speaking about Signals at Frontend Nation on Wednesday (25-minute intro to Signals at 11 am US/Pacific) and Friday (2-hour workshop on Architecting Angular apps with Signals). You can register here.

Last but not least, I’m doing a webinar with ng-conf on that same topic next Tuesday. Registrations are open here as well:

This will close my marathon of talks for June! We’ll be back to our regular weekly newsletter next week.

What’s new in Angular 18?

The Angular team announced the release of Angular 18 earlier today. You can read the official announcement for in-depth updates. In the meantime, I thought I would summarize the top 6 highlights of this new version here:

  • New events Observable available on form controls, which returns any event going on in that control, including the different form state properties (valid, pristine, etc.)

  • Replaying events with server-side rendering. When using SSR, any user actions that happen before the corresponding Javascript handler is downloaded can be replayed automatically using the provider: provideClientHydration(withEventReplay())

As always with major versions, there’s more, but this overview of Angular v18 should give you enough to get started.

Signals: Effect cleanup

You’re probably familiar with ngOnDestroy for Angular components/pipes/directives, which cleans things up when an Angular object is destroyed. A typical use case is to unsubscribe from RxJs subscriptions (though there are better-suited tools for this now) or cancel a timer.

Angular Signals introduced the effect() function to run side-effects, including RxJs Observables or timers. As a result, we might want to cancel a timer or an RxJS-based task when the effect runs again in the future, and luckily for us, Angular supports a cleanup function for our effects.

To enable the cleanup function, we pass it as a parameter to the callback registered in our effect:

Then we call it and pass our cleanup code as a param to it using an arrow function:

Here is the full example in action on Stackblitz. This example destroys a component and clears its timer thanks to the effect cleanup function: