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.