Redirecting Guards and Resolvers in Angular v18 with RedirectCommand
Tags: angular, frontend, webdev, javascript
Published: 2024-04-29 07:00:00

In the modern era of web development, creating dynamic and interactive applications has become the norm. Implementing features that are exclusive to certain users, or available under specific conditions, can be a very complex challenge.

For this reason, **Angular** offers a routing system based on **Routes**, rules and components, enabling you to can easily design your applications.

In this article, I will discuss about safeguarding **Routes** redirecting the user elsewhere, using also a new feature introduced in **Angular v18**.

But before we proceed, let's have a brief review about **Angular Router**…

---

## Angular Router Guards and Resolvers

**Angular Router** library allows you to manage navigation within your Angular application, defining a list of **Routes**.

Each **Route** is defined by a series of information, such as the path to access it, the Angular component to load, **child Routes** and much more:

```ts
import { Route } from '@angular/router';
import { MyFeatureComponent, MyFeatureGuard } from './my-feature';

const routes: Route[] = [
  {
    path: 'my-feature',
    component: MyFeatureComponent,
    canActivate: [MyFeatureGuard],
    data: {
      id: "my-feature-id"
    }
  }
];
```

You can protect one or more **Routes**, restricting the access or the exit, based on certain conditions, using functions called **Guards**:

```ts
import { Route } from '@angular/router';
import { MyService } from './my-feature';

const myRoute: Route = [
  path: 'my-feature',
  canMatch: [() => inject(MyService).canMatch()],
  canActivate: [() => inject(MyService).canActivate()],
  canActivateChild: [() => inject(MyService).canActivateChild()],
  canDeactivate: [() => inject(MyService).canDeactivate()],
];
```

There are four types of Angular **Guards**, each with a different role:

- **`canMatch`**: used to verify that a Route can be loaded. You can define multiple Routes for a single path and use this guard to select only one based on certain conditions;

- **`canActivate`**: used to determine whether a user can activate a particular Route. For example, you can use it to control access to pages reserved only for certain users;

- **`canActivateChild`**: similar to **`canActivate`**, but it controls also the access to child Routes of a main Route. It runs on every navigation towards a child Route, even if it started from another child Route;

- **`canDeactivate`**: used to verify whether a user can exit a given Route. For example, you can use it to ask for confirmation before leaving a page.

> **Note:** originally, **Guards** were implemented as **Injectable services**. Following the introduction of the **`inject()`** function, it was possible to define them as functions. As a result, **Injectable Guards are currently deprecated**.

![Order of execution of the **Guards** during navigation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6w4zgn7crd7qbtumvipz.png)

Furthermore, you can use **Resolver** functions to prepare data for a **Route**:

```ts
import { Route } from '@angular/router';
import { MyService } from './my-feature';

const myRoute: Route = [
  path: 'my-feature',
  resolve: {
    user: () => inject(MyService).getUserInfo(),
    config: () => inject(MyService).getUserConfig()
  }
];
```

Using **Resolvers** is a great approach to ensure the presence of data before accessing a **Route**, avoiding to deal with missing data in a page.

![Order of execution of **Resolvers** during navigation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rr8avp8sajtk4queqp2p.png)

---

Now that I covered the basics, let's see how to protect your **Routes** by redirecting users elsewhere.

## Use Guards and Resolvers to redirect navigation

Angular Guards allow you to prevent access or exit to one or more **Routes**, blocking navigation.

However, to ensure a smoother user experience, it is often preferable to redirect the user to another **Route**.

Thanks to **Guards**, we can achieve this very easily, starting a new navigation before blocking the current one:

```ts
import { inject } from '@angular/core';
import { Route, Router } from '@angular/router';
import { MyPage } from './pages/my-page';

const route: Route = {
  path: 'my-page',
  component: MyPage,
  canActivate: [
    () => {
      const router = inject(Router);

      router.navigate(['./my-other-page']);
      return false;
    },
  ],
};
```

You can achieve a similar result using the **Resolvers**, starting a new navigation inside them:

```ts
import { Route, Router } from '@angular/router';
import { MyService } from './my-feature';

const myRoute: Route = [
  path: 'my-feature',
  resolve: {
    user: () => {
      const router = inject(Router);

      router.navigate(['./my-other-page']);
      return null;
    }
  }
];
```

### Redirect with UrlTree

Alternatively, you can redirect navigation by having **Guards** and **Resolvers** return a **`UrlTree`** representing the new **Route**:

```ts
import { inject } from '@angular/core';
import { Route, Router, UrlTree } from '@angular/router';
import { MyPage } from './pages/my-page';

const route: Route = {
  path: 'my-page',
  component: MyPage,
  canActivate: [
    () => {
      const router: Router = inject(Router);

      const urlTree: UrlTree = router.parseUrl('./my-other-page');
      return urlTree;
    },
  ],
};
```

However, this technique does not allow you to redirect navigation using **`NavigationExtras`**, as allowed by the previous technique:

```ts
canActivate: [
  () => {
    const router = inject(Router);

    router.navigate(['./my-other-page'], { skipLocationChange: true });
    return false;
  }
]
```

### Redirect with RedirectCommand

To overcome this, **Angular v18** introduces a new **`RedirectCommand`** class capable of handling **`NavigationExtras`**, enabling you to redirect navigation in **Guards** and **Resolvers**:

```ts
import { inject } from '@angular/core';
import { RedirectCommand, Route, Router, UrlTree } from '@angular/router';
import { MyPage } from './pages/my-page';

const route: Route = {
  path: 'my-page',
  component: MyPage,
  canActivate: [
    () => {
      const router: Router = inject(Router);
      const urlTree: UrlTree = router.parseUrl('./my-other-page');

      return new RedirectCommand(urlTree, { skipLocationChange: true });
    },
  ],
};
```

The introduction of this new **`RedirectCommand`** class ensures great maintainability for **Guards** and **Resolvers**.

![New **`RedirectCommand`** class introduction](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0yagno9iolft4h8mupjt.jpeg)

Having been designed specifically for these use cases, it can be easily extended to adapt to any necessary new parameters in the future.

---

## Thanks for reading so far 🙏

I’d like to have your feedback so please leave a **comment**, **like** or **follow**. 👏

Then, if you really liked it, **share it** among your community, tech bros and whoever you want. And don’t forget to **[follow me on LinkedIn](https://www.linkedin.com/in/davide-passafaro/)**. 👋😁