DEV Community

Cover image for Redirecting Guards and Resolvers in Angular v18 with RedirectCommand
Davide Passafaro
Davide Passafaro

Posted on

Redirecting Guards and Resolvers in Angular v18 with RedirectCommand

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:

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"
    }
  }
];
Enter fullscreen mode Exit fullscreen mode

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

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()],
];
Enter fullscreen mode Exit fullscreen mode

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

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

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()
  }
];
Enter fullscreen mode Exit fullscreen mode

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


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:

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;
    },
  ],
};
Enter fullscreen mode Exit fullscreen mode

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

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;
    }
  }
];
Enter fullscreen mode Exit fullscreen mode

Redirect with UrlTree

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

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;
    },
  ],
};
Enter fullscreen mode Exit fullscreen mode

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

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

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

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:

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 });
    },
  ],
};
Enter fullscreen mode Exit fullscreen mode

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

New ** raw `RedirectCommand` endraw ** class introduction

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. 👋😁

Top comments (1)

Collapse
 
jangelodev profile image
João Angelo

Hi Davide Passafaro,
Your tips are very useful.
Thanks for sharing.