Route guards – canActivate, canActivateChild, canDeactivate

Yesterday, we introduced how route guards can control whether or not a user can navigate to a new URL in our application.

The main guard to achieve that behavior is canActivate. This guard will run every time the user tries to access a path and only allow the user to proceed if the guard function returns true.

Here’s an example that’s going to check if the user is logged in before proceeding:

If we want to redirect the user to a login component when they’re not logged in, we can do so by using the Router service and returning a UrlTree object as follows:

Note how the inject function is used in all these guards to access any service we need.

canActivate works well when navigating to components. If we want to prevent the lazy-loading of a child route or prevent access to any path of the child route, then canActivateChild is what we need instead of canActivate.

Finally, if you want to prevent the user from navigating away from the current path/component, you can implement the canDeactivate guard. A typical example of such a use case is when working with complex forms where we want to make sure the user has saved their progress before navigating away from the current screen/component:

Another exciting feature is that we can access the current component itself instead of a service, which is especially useful for canDeactivate:

If you like those new framework features, I covered some more utility router functions released with Angular 14+ in this post.

How to use Angular route guards?

The Angular router has 5 different route guards that can be used to control user navigation in the browser.

Such guards can be used in scenarios such as:

  • Preventing users from navigating to parts of an application without authorization
  • Preventing users from leaving a form before it is saved or completed
  • Making sure the user is logged in before proceeding to the next page

A guard is a function (class-based guards are deprecated as of Angular 15.2) that can return three different values:

  • true if we want to allow the user to proceed (then the navigation happens)
  • false if we want to prevent the user from proceeding (then the navigation doesn’t happen)
  • A UrlTree if we want to redirect the user to another component (usually a login component or an error component)

Sometimes, we want to ask a server about the user, which means that our guard is going to do asynchronous work. That’s fine because guards can return an Observable or a Promise of the above values, which means that a guard can return any of the six following types:

Any route can be protected by one or more guards. In the router config, we define an array of guards for each route as follows:

The above code means that in order to access the path /team/:id, a user has to satisfy the requirement of canActivateTeam, a guard function that returns one of the values documented above.

Path mapping to simplify verbose imports

When an Angular application contains several modules or sub-folders, it can become challenging to maintain the readability of your Typescript imports. For instance:

import {something} from '../../../../../shared/shared.service'

It’s always better to rely on your IDE to write those paths for you using autocompletion (usually ctrl + space). Still, imports don’t have to be that long. Typescript has a solution for that called path mapping.

The idea is to define common paths in the tsconfig.json file of your projects. Then, in the compilerOptions property, add or edit the paths config to provide some aliases for all of your long paths:

"paths": {
  "stubs/*": [
    "./src/app/stubs/*"
  ],
  "state/*": [
    "./src/app/state/*"
  ],
  "shared/*": [
    "./src/app/shared/*"
  ]
}

The above results in a shorter import syntax everywhere in your code:

import {something} from 'shared/shared.service'

Of course, you can define as many path mappings as you need. Read that post for more of my favorite Typescript tips: 5 Typescript tricks for Angular. Some have already been covered in this newsletter, but all need reminders occasionally.

Hot and cold RxJs Observables

You’ve probably heard about cold and hot observables in RxJs. What does that mean, and what’s the difference?

It’s pretty straightforward:

  • A cold observable creates a new “task” for each subscriber. For instance, observables returned by the Angular HttpClient are cold. If we subscribe to the same
    observable three times, we’re firing three different HTTP requests. Another consequence is that cold observables don’t do anything unless they get subscribed to.
  • A hot observable, as you might have guessed, is the opposite. Hot observables share their data with all subscribers. They are multicast. They don’t need to be subscribed to get started. Subjects are examples of hot observables. The observables we get from Angular FormControl are hot observables, too.

I’ll share more tips on RxJs hot and cold observables in the coming weeks.

The inject() function

Next in our dependency injection series is the inject() function. You’re probably familiar with injecting services using class constructors as follows:

We can also use the inject function to do the same:

The inject function can be used in 3 different places. We can use it when initializing the field of an “Angular” class (component/service/pipe/directive/module) or in the body of the constructor of such a class:

Or it can be used in a provider factory function (see yesterday’s post as an example):

For now, we can consider that function as a syntax alternative. That said, we must start using it as soon as possible because router class guards have been deprecated in Angular 15.2. And the alternative is functional guards that might need dependencies injected – using the inject function:

Conditional Dependency Injection

Yesterday, we saw how to use our component (and module) hierarchy to configure dependency injection with Angular.

Today, let’s look at how we can make dependency injection conditional. For instance, say we have a LoginService for production that requires features unavailable at dev time or that we do not want to use during the development phase.

What we can do then is configure our providers to make a decision based on the current environment:

Using the ternary operator [if true] ? [then this] : [else that], we can decide when to use a service over an alternative version.

If you need to make that dependency injection decision depending on other factors, such as data from other services, or if you have several different possible services to inject, you can use a factory function that relies on other services (deps) that are injected in the factory function as follows:

The order of the dependencies in deps has to match the order of the parameters in the factory function passed to useFactory.

Dependency Injection Priority and Hierarchy

Yesterday, we covered the different options to configure our providers with the providedIn syntax. Today, let’s look at how Angular resolves dependency injection using hierarchical injectors.

Let’s assume we have a ButtonComponent that needs a LoginService. Angular is going to check the injector of ButtonComponent to see if a LoginService is available there (since all components have their own injector, configurable with the array of providers or the providedIn syntax).

Suppose the component injector doesn’t know about that service. In that case, it’s going to ask its parent component, then grandparent, making its way up the component hierarchy till it gets to the root injector of the application:

These are called hierarchical injectors: They rely on our component hierarchy. This explains why changing the providers’ config at the component level impacts a component and all its children.

What about modules?

If a service cannot be found in the component tree, then Angular is going to use a similar resolution path using the module hierarchy, from the child modules all the way up to the AppModule.

If no service is found, then Angular throws an error stating no provider is available for that service.

Dependency Injection and Provider Config

As Angular developers, we use and create services fairly often. So, in today’s post, let’s demystify the following syntax and see what the different options for it are:

When a service is provided in the root injector (providedIn: 'root'), it means that such a service can be injected into any component of your application, and that service will be a singleton shared by all consumers in your application.

If we want to restrict the scope of a service, we can provide it in a module or a component instead of the root injector. The syntax looks like this – we use the class name of the targeted module or component:

When a service is provided in a module, only the components/directives/pipes/services of that module can inject that service. However, when provided in a component, that component and its children can inject that service.

From a syntax standpoint, it’s also important to mention that using providedIn is equivalent to doing the following in a component or a module:

The above code is equivalent to the following:

So you only need one or the other; both would be redundant.

Finally, there is another option available: platform.

The service would be injectable into any Angular app in the current browser tab. It’s a rare use case, but if you’re running multiple Angular applications on the same page and want to share a service between these applications, platform is what you need.

For more information, you can read that post of mine that covers all these options (note: providedIn: 'any' used to be an option but is now deprecated, which is why I didn’t mention it)

How to decide which RxJs operator to use?

There are more than 120 operators in RxJs, and with so many options, it can be challenging to decide which one we should use. We covered a few of them in this newsletter so far and mentioned websites such as Rxmarbles to learn more about these operators.

Luckily, Rxjs.dev has an interesting feature called the operator decision tree. This tool asks a few questions about what we’re trying to do and ends up suggesting one operator that answers that need.

It uses a wizard-style approach:

The tool is excellent and does save a lot of time researching operators in the documentation. I found some scenarios where it suggests deprecated options. Still, each deprecated feature has a link to its replacement, so it’s easy to find the actual operator to use from there.

Angular Signals are in the works!

This is probably the most significant change coming to the framework since the release of Angular v2 many years ago.

A new feature called Signals is being prototyped in Angular, and we can follow the entire discussion on GitHub.

Where do signals come from?

They are the answer from the Angular team to several different requests from the developer community over the years, more specifically:

  • Being able to use Observables with @Input
  • Being able to use Angular without Zone.js to have more control over change detection.
  • Having state management built-in with the framework so we don’t need libraries like NgRx or NgXs anymore.
  • Being able to use Angular in a reactive way without relying on RxJs.

In other words, Signals will be a clear and unified model for how data flows through an application and will work without any dependency (no RxJs, Zone, or other third-party library needed).

Will we have to change everything in our apps?

No, because the Angular team has communicated that:

  • Signals will be optional, and the current way of working with Angular will remain in place.
  • Signals will provide ways to play nicely with RxJs (a Signal can be turned into an Observable and vice-versa)

What will signals look like?

It’s still early to have a definite API. We’re not 100% sure what signals will look like, but the early prototype API looks like this:

Creating a signal with a default value (of 0 in this example)

The above line of code would be very much like doing const counter = new BehaviorSubject(0);

Setting a new value to a signal

Updating a value derived from the current value

Updating a value within a signal (mutation of the internal state)

Displaying the value of a signal in an HTML template or Typescript code

The above would be the equivalent of counter | async when using RxJs. The nice thing is that with Signals, there will be no more .subscribe() and .unsubscribe().

If you want to dive into more details, here is the documentation of the current prototype. Again, this is still early and I would not expect Signals to become fully available before Angular 17 or 18, but this is very exciting nevertheless!