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:

Signals custom equality functions

Angular Signals are growing almost every day with new features and new ways of building Angular applications. If you’re still unsure about signals and want to catch up, you can watch this Angular Signals workshop recording on Youtube or sign up for my continuously updated Angular Signals course.

Today, let’s explore how Angular determines if the value of a signal has changed. If we create the following signal with a default value of “Hello”:

Then calling name.set("Hello") does not trigger any updates for any of the consumers of our name signal. That’s because Angular compares the “new” value with the previous one using a === comparison, and "Hello" === "Hello" is true, so Angular doesn’t trigger any change.

What about objects? When comparing objects with ===, we’re comparing references, meaning “Are the two variables pointing at the same object?” We can verify that easily with a few lines of Javascript running in a browser console:

This leads us to the custom signal equality function. If we want to do a deep comparison instead of just comparing references, we can instruct Angular to do so by providing our own comparison function. Here is the example used by the Angular team:

Of course, we don’t need lodash to implement custom equality functions. For example, we want to create a signal that only emits updates when the current user ID changes. We don’t care if the user’s name or email changes, just the ID (which would indicate that a new user logged in, most likely):

With this approach, the signal doesn’t trigger any updates if we change values that aren’t the ID. For instance, we can secretly turn our user into Batman (and note that I do create a new object by making a copy of the current one, which would make === return false):

And yet my user remains known as Bruce after turning him into Batman (full example here on Stackblitz):

Why use custom equality functions?

Performance could be one thing: if we have a signal that stores an object and know that the only change we make to that object is one or two specific properties, then creating a custom equality function on these properties would make perfect sense. Even more so if the properties are in nested objects, though I wouldn’t recommend using deeply nested objects as signal values, keeping our values as flat as possible instead.

How to use Angular Router State?

The Angular Router supports passing custom state information with every navigation. Here is an example of using routerLink with some attached state:

As you can see, state is an @Input of the routerLink directive. You can pass any number of objects you want to that state. Such a state gets added to the browser’s history.state object, which can be helpful if you need to pass some information to a non-Angular library, for instance.

The receiving component can access the data using router.getCurrentNavigation(). The state data is then nested under extras.state:

You can find an example in action here on Stackblitz. The example is an app with a list of products. Clicking on a product opens a product details component. The selected product is passed to such component using router state instead of using a resolver and URL parameters, which leaves our router config lightweight:

The end result looks like this:

What’s an Angular view?

As Angular developers, we’re used to thinking of our application as made up of different components. The funny thing is that from the perspective of the Angular team (and compiler), an Angular application is made up of views.

What’s the difference?

The Angular documentation says it best: A view is the smallest grouping of display elements that can be created and destroyed together. Angular renders a view under the control of one or more directives (remember that a component is a specific kind of directive with an HTML template).

In other words, every component creates its own view, and such a view can be made of other views. For instance, when you use a structural directive such as ngIf or ngFor, you’re creating a new view.

Why does it matter?

Because of Signals. The Angular team is working hard on signal-based components for several reasons, one of them being improved change detection that will apply at the view level instead of the component level. This means that when we use signal-based components, the granularity of change detection will be significantly more accurate.

Say, for instance, you have a component with 100 lines of HTML code in its template, and two lines in that template get displayed conditionally based on the value of a signal. With the signal-based approach, changing the value of that signal will result in Angular trying to render just those two lines of the HTML template impacted by the signal update and not bothering about the other 98 lines of code.

When compared to the current default Zone.js change detection, which would check the entire component tree for changes, you can see how massive a difference signal-based components will make. That is why I keep making the point that everyone should be migrating to signals. They are a true game changer for the performance and maintainability of our Angular applications.

Understanding Angular/Typescript types

First, some news: I’m running a public 5 half-day online Angular class during the week of April 22nd. It’s the perfect class if you’re new to Angular or have some experience and want to ensure you know about all the fundamentals. It’s the ideal class to prepare for the Angular Level 1 certification exam.

On a side note, I can give private talks for your company or dev team on any topic, including Signals, the future of Angular, and more. Just email me for more info if you’d like to get such a talk planned in the future.

Today, I want to cover a tricky topic for many developers I interact with: Reading and understanding Typescript type definitions from the Angular framework.

For instance, let’s look at the type definition of the canActivate function:

We can tell CanActivateFn is a function because:

  1. The Angular team uses the Fn suffix as a convention for all functions
  2. The type signature is ( params ) => returnType, which is how TypeScript defines types for functions. The arrow symbol => is key there.

So, in that case, the function has two parameters, route of type ActivatedRouteSnapshot, and state of type RouterStateSnapshot. The function returns a type MaybeAsync<GuardResult>.

Here is what the type definition of MaybeAsync looks like:

What does that mean? MaybeAsync<T> is a generic type, which means it works with any number of types, referred to as T here. You can see T as a type variable that gets replaced with an actual value decided when we use that type. For instance, if I end up using MaybeAsync on a string, T becomes string, and our type definition means:

type MaybeAsync<string> = string | Observable<string> | Promise<string>

So MaybeAsync<string> can be either a string, an Observable that will return a string, or a Promise that will return a string. That’s because the | character defines a union type and can be seen as a logical OR. In other words:

MaybeAsync<string> IS A string OR Observable<string> OR Promise<string>.

Now, in the case of our CanActivate function, the return type is MaybeAsync<GuardResult>. On angular.io, most types are clickable (a lot less on angular.dev for now). If I click on GuardResult on this page, I get to the following documentation entry:

So a GuardResult is either a boolean or a UrlTree. This tells us that a CanActivate function can return six possible different types:

  • a boolean, an Observable of a boolean, a Promise of a boolean
  • a UrlTree, an Observable of a UrlTree, a Promise of a UrlTree.

In the past, the documentation would list the six types inline as follows, which is the same thing but a little harder to read. Additional types have been added in Angular 16 to improve the readability of such framework APIs:

Another tricky thing with types is that several features of the Angular framework support different options, resulting in multiple types of signatures. You can look at the toSignal function for such an example – there are 5 different overloads in that function signature, the most basic one being:

As an exercise, I invite you to examine the 5 overloads and try to understand where they come from and why they make sense.

If you encounter any other tricky type you can’t decipher, please send it my way, and I’ll be happy to cover it in a future newsletter entry.

Recap of ng-conf 2024

Ng-conf 2024 wrapped up a few hours ago, and the event was filled with great news for Angular enthusiasts. First, the forecast for the following few versions looks very bright:

The Angular team went on to reveal more of these expected features by showing what they’re currently working on:

For the first time, the team also mentioned a future where Angular would run “without Zone.js and RxJs.” Though we knew that zoneless was in the plans for quite some time, it looks like the very positive reception of Angular Signals has pushed the team to envision removing RxJs as a dependency.

The surprising Wiz announcement

On that same topic of Angular Signals, the Angular team made a surprise announcement of their close collaboration with Wiz, a framework used internally at Google for most public apps that everyone knows about: Youtube, Google Photos, Drive, Gmail, etc. That collaboration started with signals, which are shared between Wix and Angular. The big announcement was that Angular Signals are now used in 100% of Youtube, which is quite impressive considering that some people were thinking that Angular was dead not too long ago.

On that same topic, the Angular team revealed their internal motto: build Angular for the next ten years and ensure that it is still an excellent choice ten years from now. Now that sunny forecast makes perfect sense!

Server-side rendering is all the rage

37% more apps have been using server-side rendering since the launch of Angular v17. It’s the topic I talked about at the conference and will be the topic of one of my upcoming free monthly online workshops. In the meantime, you can read my Angular server-side rendering tutorial here.

The great news is that the Angular team is working with Wiz on that topic as well to implement partial hydration soon, which will give us more options on server-side rendering and lazy-loading with defer.

Component authoring improvements

The Angular team is also working on improving the Angular component syntax by possibly removing a lot of boilerplate:

No more selector, no more imports array, no more standalone: true! Again, this is just an idea at this point, but let’s remember that signals were a similar idea not too long ago and are now a fully available API in the framework.

As a recap, there’s much to be excited about for us Angular developers. I will cover more updates in this newsletter over the next few weeks, as there is more to unpack from these two conference days.

You can watch a replay of the main keynote here on YouTube (powered by Angular Signals!)

Code challenge #3 solution

Before I dive into the solution of code challenge #3, I wanted to mention that my next FREE workshop is scheduled for April 4th. It will be all about getting into RxJs from scratch, and you can register here. I’ll start from the basics and move on to more advanced topics, such as subjects and operators, and you’ll be able to ask me questions as we go through the content and exercises together.

As always, it’s a free workshop, but your coffee donations are always welcome.

Let’s get into our code challenge solution. The goal was to “connect” two different dropdowns so that the selection of the first dropdown (a continent) would update the contents of the second dropdown (a list of countries for that continent):

The challenge is that countries depend on two different Observable data sources:

  1. An HTTP request to an API to get the list of all countries in the world
  2. The currently selected continent (a reactive form select dropdown)

In other words, we start with the following code:

Our goal is to combine data from both Observables into one result. Ideally, such a result would be updated whenever any of the Observable sources are updated (e.g., a new continent is selected by the user, or a new list of countries is available from the API).

As a result, we can use the withLatestFrom operator to make that “connection” between our two Observables:

Such an operator returns an array of all the latest values of our source Observables in the order in which they were declared. As a result, we can use the map operator to receive that array and turn it into something different:

Inside that operator, we decide to return a new array. The continent remains the same, and we filter the list of countries based on that continent:

FInally, we can use tap to run some side effects. In our case, we update the list of countries used by the dropdown, and we set the country dropdown value to the first country of that list to ensure that a country from a previous continent doesn’t remain selected:

And that’s it! You can see that code in action on Stackblitz here.

A few of you sent me their own solutions to the challenge, and a recurring theme was that most of you didn’t “connect” the Observables and just assumed that the list of countries would already be downloaded and available by the time the user selected a continent. Something along those lines, where this.countries would be received from the API before that code runs:

While the above code would work most of the time, if a user selects a continent before this.countries is defined, an error would be thrown and the corresponding subscription destroyed, which means the above code would not run anymore when the user selects a new value. As a result, using operators is safer.

Another trick in my solution is that I didn’t use any .subscribe in my code, which means I don’t have to unsubscribe, as I let the async pipe do so for me automatically:

That’s it for code challenge #3! Next week, I’ll send you updates from ng-conf in Salt Lake City. If you’re around, feel ree to come say hi!