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"
}
}
];
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()],
];
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 tocanActivate
, 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.
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()
}
];
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.
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;
},
],
};
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;
}
}
];
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;
},
],
};
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;
}
]
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 });
},
],
};
The introduction of this new RedirectCommand
class ensures great maintainability for Guards and Resolvers.
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)
Hi Davide Passafaro,
Your tips are very useful.
Thanks for sharing.